forge-openclaw-plugin 0.2.82 → 0.2.84

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.
package/dist/index.html CHANGED
@@ -13,14 +13,14 @@
13
13
  />
14
14
  <link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
15
15
  <link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
16
- <script type="module" crossorigin src="/forge/assets/index-DEoJdpz5.js"></script>
16
+ <script type="module" crossorigin src="/forge/assets/index-D7BNRYkR.js"></script>
17
17
  <link rel="modulepreload" crossorigin href="/forge/assets/vendor-BVU0cZC9.js">
18
18
  <link rel="modulepreload" crossorigin href="/forge/assets/board-DKxKOwax.js">
19
19
  <link rel="modulepreload" crossorigin href="/forge/assets/ui-3Wd4pVaA.js">
20
20
  <link rel="modulepreload" crossorigin href="/forge/assets/motion-CM4AfIqo.js">
21
21
  <link rel="modulepreload" crossorigin href="/forge/assets/table-BUeQ9wzR.js">
22
22
  <link rel="stylesheet" crossorigin href="/forge/assets/vendor-B-Lq_OG3.css">
23
- <link rel="stylesheet" crossorigin href="/forge/assets/index-Chc9CWm5.css">
23
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-NqIbz_lv.css">
24
24
  </head>
25
25
  <body class="bg-canvas text-ink antialiased">
26
26
  <div id="root"></div>
@@ -41,7 +41,12 @@ const generatedHealthEventTemplateSchema = z.object({
41
41
  enabled: z.boolean().default(false),
42
42
  workoutType: z.string().trim().min(1).default("workout"),
43
43
  title: z.string().trim().default(""),
44
- durationMinutes: z.number().int().positive().max(24 * 60).default(45),
44
+ durationMinutes: z
45
+ .number()
46
+ .int()
47
+ .positive()
48
+ .max(24 * 60)
49
+ .default(45),
45
50
  xpReward: z.number().int().min(0).max(500).default(0),
46
51
  tags: z.array(z.string().trim()).default([]),
47
52
  links: z.array(healthLinkSchema).default([]),
@@ -110,7 +115,12 @@ const companionSourceStatesSchema = z.object({
110
115
  export const createCompanionPairingSessionSchema = z.object({
111
116
  label: z.string().trim().default("Forge Companion"),
112
117
  userId: z.string().trim().nullable().optional(),
113
- expiresInMinutes: z.coerce.number().int().min(5).max(24 * 60).default(30),
118
+ expiresInMinutes: z.coerce
119
+ .number()
120
+ .int()
121
+ .min(5)
122
+ .max(24 * 60)
123
+ .default(30),
114
124
  transportMode: z
115
125
  .enum(["iroh", "manual-http"])
116
126
  .default("iroh")
@@ -221,7 +231,11 @@ export const mobileHealthSyncSchema = z.object({
221
231
  endedAt: z.string().datetime(),
222
232
  sourceTimezone: z.string().trim().min(1).default("UTC"),
223
233
  localDateKey: z.string().trim().min(1),
224
- providerRecordType: z.string().trim().min(1).default("healthkit_sleep_sample"),
234
+ providerRecordType: z
235
+ .string()
236
+ .trim()
237
+ .min(1)
238
+ .default("healthkit_sleep_sample"),
225
239
  rawStage: z.string().trim().min(1),
226
240
  rawValue: z.number().int().nullable().optional(),
227
241
  payload: z.record(z.string(), z.unknown()).default({}),
@@ -276,6 +290,7 @@ const mobileHealthSyncFamilySchema = z.enum([
276
290
  "sleep_segments",
277
291
  "sleep_raw_records",
278
292
  "workout_summaries",
293
+ "workout_archive",
279
294
  "workout_time_series",
280
295
  "workout_routes",
281
296
  "workout_tombstones",
@@ -293,14 +308,13 @@ export const mobileHealthSyncSessionStartSchema = mobileHealthSyncSchema
293
308
  sourceStates: true
294
309
  })
295
310
  .extend({
296
- schemaVersion: z
297
- .string()
298
- .trim()
299
- .default(HEALTH_MOBILE_SYNC_SCHEMA_VERSION),
311
+ schemaVersion: z.string().trim().default(HEALTH_MOBILE_SYNC_SCHEMA_VERSION),
300
312
  requestedFamilies: z
301
313
  .array(mobileHealthSyncFamilySchema)
302
314
  .default(defaultMobileHealthSyncFamilies),
303
- expectedCounts: z.record(z.string(), z.number().int().nonnegative()).default({}),
315
+ expectedCounts: z
316
+ .record(z.string(), z.number().int().nonnegative())
317
+ .default({}),
304
318
  metadata: z.record(z.string(), z.unknown()).default({})
305
319
  });
306
320
  const workoutTimeSeriesChunkPayloadSchema = z.object({
@@ -354,7 +368,9 @@ export const mobileHealthSyncChunkSchema = z.object({
354
368
  });
355
369
  export const mobileHealthSyncSessionCompleteSchema = z.object({
356
370
  finalCursor: z.record(z.string(), z.unknown()).default({}),
357
- expectedCounts: z.record(z.string(), z.number().int().nonnegative()).default({})
371
+ expectedCounts: z
372
+ .record(z.string(), z.number().int().nonnegative())
373
+ .default({})
358
374
  });
359
375
  export const verifyCompanionPairingSchema = z.object({
360
376
  sessionId: z.string().trim().min(1),
@@ -534,9 +550,9 @@ function upsertPairingSourceState(pairing, source, patch) {
534
550
  ...patch.metadata
535
551
  }
536
552
  : safeJsonParse(current.metadata_json, {});
537
- const nextDesiredEnabled = patch.desiredEnabled ?? (current.desired_enabled === 1);
538
- const nextAppliedEnabled = patch.appliedEnabled ?? (current.applied_enabled === 1);
539
- const nextSyncEligible = patch.syncEligible ?? (current.sync_eligible === 1);
553
+ const nextDesiredEnabled = patch.desiredEnabled ?? current.desired_enabled === 1;
554
+ const nextAppliedEnabled = patch.appliedEnabled ?? current.applied_enabled === 1;
555
+ const nextSyncEligible = patch.syncEligible ?? current.sync_eligible === 1;
540
556
  const nextUpdatedAt = nowIso();
541
557
  getDatabase()
542
558
  .prepare(`UPDATE companion_pairing_source_states
@@ -680,9 +696,7 @@ function computeSleepDerivedMetrics(input) {
680
696
  })
681
697
  .reduce((total, stage) => total + stage.seconds, 0);
682
698
  const restorativeShare = input.asleepSeconds > 0 ? restorativeSeconds / input.asleepSeconds : 0;
683
- const sleepDebtHours = input.asleepSeconds > 0
684
- ? Math.max(0, 8 - input.asleepSeconds / 3600)
685
- : 8;
699
+ const sleepDebtHours = input.asleepSeconds > 0 ? Math.max(0, 8 - input.asleepSeconds / 3600) : 8;
686
700
  return {
687
701
  durationHours: round(input.asleepSeconds / 3600, 2),
688
702
  efficiency: round(efficiency, 3),
@@ -1112,7 +1126,10 @@ function inferHistoricalRawStage(payload) {
1112
1126
  return "asleep_unspecified";
1113
1127
  }
1114
1128
  function inferHistoricalSleepBucket(stage) {
1115
- const normalized = stage.trim().toLowerCase().replace(/[\s-]+/g, "_");
1129
+ const normalized = stage
1130
+ .trim()
1131
+ .toLowerCase()
1132
+ .replace(/[\s-]+/g, "_");
1116
1133
  if (normalized.includes("bed")) {
1117
1134
  return "in_bed";
1118
1135
  }
@@ -1154,22 +1171,24 @@ function sleepHasReflection(session) {
1154
1171
  tags.length > 0);
1155
1172
  }
1156
1173
  function sleepEfficiency(session) {
1157
- return typeof session.derived.efficiency === "number"
1174
+ return typeof session.derived.efficiency ===
1175
+ "number"
1158
1176
  ? session.derived.efficiency
1159
1177
  : session.timeInBedSeconds > 0
1160
1178
  ? session.asleepSeconds / session.timeInBedSeconds
1161
1179
  : 0;
1162
1180
  }
1163
1181
  function sleepRestorativeShare(session) {
1164
- return typeof session.derived.restorativeShare ===
1165
- "number"
1182
+ return typeof session.derived
1183
+ .restorativeShare === "number"
1166
1184
  ? session.derived.restorativeShare
1167
1185
  : 0;
1168
1186
  }
1169
1187
  function sleepRecoveryState(session) {
1170
1188
  return typeof session.derived.recoveryState ===
1171
1189
  "string"
1172
- ? (session.derived.recoveryState || null)
1190
+ ? session.derived.recoveryState ||
1191
+ null
1173
1192
  : null;
1174
1193
  }
1175
1194
  function sleepQualitativeState(session) {
@@ -1210,7 +1229,9 @@ function sleepStageShare(session) {
1210
1229
  return session.stageBreakdown.map((stage) => ({
1211
1230
  stage: stage.stage,
1212
1231
  seconds: stage.seconds,
1213
- percentage: session.asleepSeconds > 0 ? round(stage.seconds / session.asleepSeconds, 3) : 0
1232
+ percentage: session.asleepSeconds > 0
1233
+ ? round(stage.seconds / session.asleepSeconds, 3)
1234
+ : 0
1214
1235
  }));
1215
1236
  }
1216
1237
  function buildSleepSurfaceNight(session, baselineAverageSleepSeconds) {
@@ -1238,8 +1259,8 @@ function buildSleepSurfaceNight(session, baselineAverageSleepSeconds) {
1238
1259
  hasRawSegments: session.rawSegmentCount > 0,
1239
1260
  qualitySummary: typeof session.annotations.qualitySummary ===
1240
1261
  "string"
1241
- ? (session.annotations.qualitySummary ||
1242
- null)
1262
+ ? session.annotations
1263
+ .qualitySummary || null
1243
1264
  : null,
1244
1265
  stageBreakdown: sleepStageShare(session)
1245
1266
  };
@@ -1288,7 +1309,10 @@ function pickDisplaySleepSessions(sessions) {
1288
1309
  return [...byDateKey.values()].sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));
1289
1310
  }
1290
1311
  function normalizeTimelineStage(stage, bucket) {
1291
- const normalized = stage.trim().toLowerCase().replace(/[\s-]+/g, "_");
1312
+ const normalized = stage
1313
+ .trim()
1314
+ .toLowerCase()
1315
+ .replace(/[\s-]+/g, "_");
1292
1316
  if (bucket === "in_bed" || normalized.includes("bed")) {
1293
1317
  return "in_bed";
1294
1318
  }
@@ -1559,8 +1583,7 @@ export function revokeCompanionPairingSession(pairingSessionId, activity) {
1559
1583
  }
1560
1584
  export function revokeAllCompanionPairingSessions(input, activity) {
1561
1585
  const parsed = revokeAllCompanionPairingSessionsSchema.parse(input ?? {});
1562
- const rows = listPairingRows(parsed.userIds.length > 0 ? parsed.userIds : undefined)
1563
- .filter((row) => parsed.includeRevoked || row.status !== "revoked");
1586
+ const rows = listPairingRows(parsed.userIds.length > 0 ? parsed.userIds : undefined).filter((row) => parsed.includeRevoked || row.status !== "revoked");
1564
1587
  const sessions = revokePairingRows(rows, {
1565
1588
  actor: activity?.actor ?? null,
1566
1589
  source: activity?.source ?? "ui",
@@ -1793,7 +1816,8 @@ function normalizeSleepSegmentInput(input) {
1793
1816
  return {
1794
1817
  ...input,
1795
1818
  sourceTimezone,
1796
- localDateKey: input.localDateKey || localDateKeyForTimezone(input.endedAt, sourceTimezone)
1819
+ localDateKey: input.localDateKey ||
1820
+ localDateKeyForTimezone(input.endedAt, sourceTimezone)
1797
1821
  };
1798
1822
  }
1799
1823
  function normalizeSleepRawRecordInput(input) {
@@ -1801,7 +1825,8 @@ function normalizeSleepRawRecordInput(input) {
1801
1825
  return {
1802
1826
  ...input,
1803
1827
  sourceTimezone,
1804
- localDateKey: input.localDateKey || localDateKeyForTimezone(input.endedAt, sourceTimezone)
1828
+ localDateKey: input.localDateKey ||
1829
+ localDateKeyForTimezone(input.endedAt, sourceTimezone)
1805
1830
  };
1806
1831
  }
1807
1832
  function listNormalizedSleepNights(payload) {
@@ -2017,8 +2042,7 @@ function upsertVitalDaySummary(userId, input) {
2017
2042
  }
2018
2043
  function summarizeUserHealthDay(userId, dateKeyValue) {
2019
2044
  const sleeps = listSleepRows([userId]).filter((row) => sleepSessionDateKey(row) === dateKeyValue ||
2020
- localDateKeyForTimezone(row.started_at, resolveTimeZone(row.source_timezone)) ===
2021
- dateKeyValue);
2045
+ localDateKeyForTimezone(row.started_at, resolveTimeZone(row.source_timezone)) === dateKeyValue);
2022
2046
  const workouts = listWorkoutRows([userId]).filter((row) => dayKey(row.started_at) === dateKeyValue);
2023
2047
  const totalSleepSeconds = sleeps.reduce((sum, row) => sum + row.asleep_seconds, 0);
2024
2048
  const totalWorkoutSeconds = workouts.reduce((sum, row) => sum + row.duration_seconds, 0);
@@ -2146,12 +2170,16 @@ function insertOrUpdateWorkoutSession(pairing, input) {
2146
2170
  (typeof existingProvenance.sourceProductType === "string"
2147
2171
  ? existingProvenance.sourceProductType
2148
2172
  : null),
2149
- activity: input.activity ?? existingDerived.activity ?? existingProvenance.activity,
2173
+ activity: input.activity ??
2174
+ existingDerived.activity ??
2175
+ existingProvenance.activity,
2150
2176
  details: input.details ?? existingDerived.details ?? existingProvenance.details
2151
2177
  });
2152
2178
  const mergedLinks = mergeHealthLinks(existingLinks, input.links, annotations.links);
2153
2179
  const mergedTags = mergeStringLists(existingTags, annotations.tags);
2154
- const nextSubjectiveEffort = matchedGenerated.subjective_effort ?? annotations.subjectiveEffort ?? null;
2180
+ const nextSubjectiveEffort = matchedGenerated.subjective_effort ??
2181
+ annotations.subjectiveEffort ??
2182
+ null;
2155
2183
  const nextMoodBefore = matchedGenerated.mood_before || annotations.moodBefore;
2156
2184
  const nextMoodAfter = matchedGenerated.mood_after || annotations.moodAfter;
2157
2185
  const nextMeaningText = matchedGenerated.meaning_text || annotations.meaningText;
@@ -2200,7 +2228,8 @@ function insertOrUpdateWorkoutSession(pairing, input) {
2200
2228
  }), matchedGenerated.generated_from_habit_id ? "merged" : "standalone", now, matchedGenerated.id);
2201
2229
  persistWorkoutEvidenceForInput(matchedGenerated.id, pairing.user_id, input);
2202
2230
  return {
2203
- mode: matchedGenerated.generated_from_habit_id || matchedGenerated.source !== "apple_health"
2231
+ mode: matchedGenerated.generated_from_habit_id ||
2232
+ matchedGenerated.source !== "apple_health"
2204
2233
  ? "merged"
2205
2234
  : "updated",
2206
2235
  id: matchedGenerated.id
@@ -2363,13 +2392,14 @@ function backfillHistoricalSleepEvidence() {
2363
2392
  )
2364
2393
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
2365
2394
  .run(sourceRecordId, rawLog.import_run_id, rawLog.pairing_session_id, session.id, rawLog.user_id, "apple_health", "historical_import_interval", rawLog.external_uid ?? rawLog.id, session.source_device, sourceTimezone, localDateKey, rawLog.started_at ?? session.started_at, rawLog.ended_at ?? session.ended_at, inferHistoricalRawStage(payload), null, "historical_import", JSON.stringify(payload), JSON.stringify({
2366
- ...(safeJsonParse(rawLog.metadata_json, {})),
2395
+ ...safeJsonParse(rawLog.metadata_json, {}),
2367
2396
  migratedFromRawLogId: rawLog.id
2368
2397
  }), rawLog.created_at);
2369
2398
  }
2370
2399
  }
2371
2400
  if (existingSegmentCount.count === 0 && rawLogs.length > 0) {
2372
- const authoritativeLogs = selectAuthoritativeSleepRows(rawLogs.filter((row) => typeof row.started_at === "string" && typeof row.ended_at === "string"));
2401
+ const authoritativeLogs = selectAuthoritativeSleepRows(rawLogs.filter((row) => typeof row.started_at === "string" &&
2402
+ typeof row.ended_at === "string"));
2373
2403
  for (const rawLog of authoritativeLogs) {
2374
2404
  const payload = safeJsonParse(rawLog.payload_json, {});
2375
2405
  const inferredStage = inferHistoricalRawStage(payload);
@@ -2498,7 +2528,9 @@ function ensureLegacyAppleSleepHistoryRepaired() {
2498
2528
  for (const cluster of clusters) {
2499
2529
  const rowsForNight = selectAuthoritativeSleepRows(cluster);
2500
2530
  const startedAt = rowsForNight[0].started_at;
2501
- const endedAt = rowsForNight.reduce((latest, row) => Date.parse(row.ended_at) > Date.parse(latest) ? row.ended_at : latest, rowsForNight[0].ended_at);
2531
+ const endedAt = rowsForNight.reduce((latest, row) => Date.parse(row.ended_at) > Date.parse(latest)
2532
+ ? row.ended_at
2533
+ : latest, rowsForNight[0].ended_at);
2502
2534
  const sourceTimezone = inferHistoricalSleepTimeZone(cluster.map((row) => row.source_timezone));
2503
2535
  const localDateKey = localDateKeyForTimezone(endedAt, sourceTimezone);
2504
2536
  const asleepSeconds = unionDurationSeconds(rowsForNight.map((row) => ({
@@ -2852,6 +2884,12 @@ function summarizeChunkPayload(family, payload) {
2852
2884
  return { sleepRawRecords: payload.sleepRawRecords?.length ?? 0 };
2853
2885
  case "workout_summaries":
2854
2886
  return { workouts: payload.workouts?.length ?? 0 };
2887
+ case "workout_archive":
2888
+ return {
2889
+ workouts: payload.workouts?.length ?? 0,
2890
+ samples: payload.workouts?.reduce((sum, workout) => sum + workout.timeSeriesSamples.length, 0) ?? 0,
2891
+ routePoints: payload.workouts?.reduce((sum, workout) => sum + workout.routePoints.length, 0) ?? 0
2892
+ };
2855
2893
  case "workout_time_series":
2856
2894
  return {
2857
2895
  workouts: payload.workoutTimeSeries?.length ?? 0,
@@ -2992,24 +3030,34 @@ export function startMobileHealthSyncSession(payload) {
2992
3030
  if (existing &&
2993
3031
  existing.pairing_session_id === pairing.id &&
2994
3032
  existing.status === "running") {
2995
- const receivedChunkIds = getDatabase()
2996
- .prepare(`SELECT chunk_id
2997
- FROM health_mobile_sync_chunks
2998
- WHERE sync_session_id = ?
2999
- ORDER BY sequence ASC`)
3000
- .all(resumeSyncSessionId)
3001
- .map((row) => row.chunk_id);
3002
- return {
3003
- syncSessionId: resumeSyncSessionId,
3004
- schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
3005
- chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
3006
- chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
3007
- chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
3008
- acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
3009
- supportsCompression: true,
3010
- acceptedFamilies: safeJsonParse(existing.requested_families_json, parsed.requestedFamilies),
3011
- receivedChunkIds
3012
- };
3033
+ const existingFamilies = safeJsonParse(existing.requested_families_json, []);
3034
+ const canResume = existing.schema_version === HEALTH_MOBILE_SYNC_SCHEMA_VERSION &&
3035
+ parsed.requestedFamilies.every((family) => existingFamilies.includes(family));
3036
+ if (canResume) {
3037
+ const receivedChunkIds = getDatabase()
3038
+ .prepare(`SELECT chunk_id
3039
+ FROM health_mobile_sync_chunks
3040
+ WHERE sync_session_id = ?
3041
+ ORDER BY sequence ASC`)
3042
+ .all(resumeSyncSessionId)
3043
+ .map((row) => row.chunk_id);
3044
+ return {
3045
+ syncSessionId: resumeSyncSessionId,
3046
+ schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
3047
+ chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
3048
+ chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
3049
+ chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
3050
+ acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
3051
+ supportsCompression: true,
3052
+ acceptedFamilies: existingFamilies,
3053
+ receivedChunkIds
3054
+ };
3055
+ }
3056
+ getDatabase()
3057
+ .prepare(`UPDATE health_mobile_sync_sessions
3058
+ SET status = 'aborted', aborted_at = ?, updated_at = ?
3059
+ WHERE id = ?`)
3060
+ .run(nowIso(), nowIso(), existing.id);
3013
3061
  }
3014
3062
  }
3015
3063
  const now = nowIso();
@@ -3075,7 +3123,8 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
3075
3123
  actualBytes: actualByteCount
3076
3124
  });
3077
3125
  }
3078
- if (wirePayload.mode !== "legacy_payload_object" && parsed.byteCount !== actualByteCount) {
3126
+ if (wirePayload.mode !== "legacy_payload_object" &&
3127
+ parsed.byteCount !== actualByteCount) {
3079
3128
  console.warn("[healthkit-sync] chunk byte count mismatch", {
3080
3129
  syncSessionId,
3081
3130
  chunkId: parsed.chunkId,
@@ -3471,7 +3520,8 @@ export function ingestMobileHealthSync(payload) {
3471
3520
  for (const sleep of normalizedSleepNights) {
3472
3521
  const targetSessionId = sleepSessionsByLocalDate
3473
3522
  .get(sleep.localDateKey)
3474
- ?.find((session) => session.startedAt === sleep.startedAt && session.endedAt === sleep.endedAt)?.id ?? null;
3523
+ ?.find((session) => session.startedAt === sleep.startedAt &&
3524
+ session.endedAt === sleep.endedAt)?.id ?? null;
3475
3525
  if (targetSessionId) {
3476
3526
  replaceHistoricalSleepSessionsForDate(pairing.user_id, sleep.localDateKey, targetSessionId);
3477
3527
  }
@@ -3559,7 +3609,9 @@ export function ingestMobileHealthSync(payload) {
3559
3609
  screenTimeDaySummaries: parsed.screenTime.daySummaries.length,
3560
3610
  screenTimeHourlySegments: parsed.screenTime.hourlySegments.length,
3561
3611
  createdCount: createdCount + movementSync.createdCount,
3562
- updatedCount: updatedCount + movementSync.updatedCount + screenTimeSync.updatedCount,
3612
+ updatedCount: updatedCount +
3613
+ movementSync.updatedCount +
3614
+ screenTimeSync.updatedCount,
3563
3615
  mergedCount
3564
3616
  }
3565
3617
  });
@@ -3575,8 +3627,12 @@ export function ingestMobileHealthSync(payload) {
3575
3627
  workouts: parsed.workouts.length,
3576
3628
  vitalsDaySummaries: parsed.vitals.daySummaries.length,
3577
3629
  vitalsMetricEntries: vitalMetricEntries,
3578
- createdCount: createdCount + movementSync.createdCount + screenTimeSync.createdCount,
3579
- updatedCount: updatedCount + movementSync.updatedCount + screenTimeSync.updatedCount,
3630
+ createdCount: createdCount +
3631
+ movementSync.createdCount +
3632
+ screenTimeSync.createdCount,
3633
+ updatedCount: updatedCount +
3634
+ movementSync.updatedCount +
3635
+ screenTimeSync.updatedCount,
3580
3636
  mergedCount,
3581
3637
  movementStays: parsed.movement.stays.length,
3582
3638
  movementTrips: parsed.movement.trips.length,
@@ -3670,10 +3726,12 @@ export function getCompanionOverview(userIds) {
3670
3726
  return (session.links.length > 0 ||
3671
3727
  (typeof annotations.qualitySummary === "string" &&
3672
3728
  annotations.qualitySummary.length > 0) ||
3673
- (typeof annotations.notes === "string" && annotations.notes.length > 0) ||
3729
+ (typeof annotations.notes === "string" &&
3730
+ annotations.notes.length > 0) ||
3674
3731
  tags.length > 0);
3675
3732
  }).length,
3676
- linkedWorkouts: workouts.filter((session) => session.links.length > 0).length,
3733
+ linkedWorkouts: workouts.filter((session) => session.links.length > 0)
3734
+ .length,
3677
3735
  habitGeneratedWorkouts: workouts.filter((session) => session.sourceType === "habit_generated").length,
3678
3736
  reconciledWorkouts: workouts.filter((session) => session.reconciliationStatus === "merged").length,
3679
3737
  vitalsDaySummaries: vitalsRows.length,
@@ -3733,9 +3791,9 @@ export function getSleepViewData(userIds) {
3733
3791
  ? session.asleepSeconds / session.timeInBedSeconds
3734
3792
  : 0)), 2),
3735
3793
  averageRestorativeShare: round(average(weekly.map((session) => sleepRestorativeShare(session))), 2),
3736
- reflectiveNightCount: weekly.filter((session) => sleepHasReflection(session))
3794
+ reflectiveNightCount: weekly.filter((session) => sleepHasReflection(session)).length,
3795
+ linkedNightCount: weekly.filter((session) => session.links.length > 0)
3737
3796
  .length,
3738
- linkedNightCount: weekly.filter((session) => session.links.length > 0).length,
3739
3797
  averageBedtimeConsistencyMinutes: Math.round(average(weekly
3740
3798
  .map((session) => session.bedtimeConsistencyMinutes)
3741
3799
  .filter((value) => value !== null))),
@@ -3764,8 +3822,10 @@ export function getSleepViewData(userIds) {
3764
3822
  .map((session) => ({
3765
3823
  id: session.id,
3766
3824
  dateKey: session.localDateKey,
3767
- onsetHour: getTimeZoneParts(session.startedAt, session.sourceTimezone).hour,
3768
- wakeHour: getTimeZoneParts(session.endedAt, session.sourceTimezone).hour,
3825
+ onsetHour: getTimeZoneParts(session.startedAt, session.sourceTimezone)
3826
+ .hour,
3827
+ wakeHour: getTimeZoneParts(session.endedAt, session.sourceTimezone)
3828
+ .hour,
3769
3829
  sleepHours: Number((session.asleepSeconds / 3600).toFixed(2))
3770
3830
  }))
3771
3831
  .reverse(),
@@ -3907,14 +3967,14 @@ export function getFitnessViewData(userIds) {
3907
3967
  averageEffort: round(average(recent
3908
3968
  .map((session) => session.subjectiveEffort)
3909
3969
  .filter((value) => value !== null)), 1),
3910
- linkedSessionCount: recent.filter((session) => session.links.length > 0).length,
3970
+ linkedSessionCount: recent.filter((session) => session.links.length > 0)
3971
+ .length,
3911
3972
  plannedSessionCount: recent.filter((session) => session.plannedContext.trim().length > 0).length,
3912
3973
  importedSessionCount: recent.filter((session) => session.source === "apple_health").length,
3913
3974
  habitGeneratedSessionCount: recent.filter((session) => session.sourceType === "habit_generated").length,
3914
3975
  reconciledSessionCount: recent.filter((session) => session.reconciliationStatus === "merged").length,
3915
3976
  topWorkoutType: orderedWorkoutTypes[0]?.[0] ?? null,
3916
- topWorkoutTypeLabel: recent.find((session) => session.workoutType === orderedWorkoutTypes[0]?.[0])
3917
- ?.workoutTypeLabel ?? null,
3977
+ topWorkoutTypeLabel: recent.find((session) => session.workoutType === orderedWorkoutTypes[0]?.[0])?.workoutTypeLabel ?? null,
3918
3978
  streakDays: Array.from(new Set(weekly.map((session) => dayKey(session.startedAt)))).length,
3919
3979
  averageHeartRateCoverage: heartRateCoverageCount > 0
3920
3980
  ? Number((heartRateCoverageSum / heartRateCoverageCount).toFixed(3))
@@ -3935,17 +3995,16 @@ export function getFitnessViewData(userIds) {
3935
3995
  energyKcal: Math.round(session.totalEnergyKcal ?? session.activeEnergyKcal ?? 0),
3936
3996
  zoneDurations: session.analytics
3937
3997
  ?.zoneDurations ?? [],
3938
- trainingLoad: (session.analytics
3939
- ?.load?.trimp ?? null),
3940
- heartRateCoverage: (session.analytics?.dataQuality?.sampleCoverage ?? 0)
3998
+ trainingLoad: session.analytics?.load?.trimp ?? null,
3999
+ heartRateCoverage: session.analytics?.dataQuality?.sampleCoverage ?? 0
3941
4000
  }))
3942
4001
  .reverse(),
3943
4002
  typeBreakdown: orderedWorkoutTypes.map(([workoutType, metrics]) => ({
3944
4003
  workoutType,
3945
- workoutTypeLabel: recent.find((session) => session.workoutType === workoutType)?.workoutTypeLabel ??
3946
- workoutType,
3947
- activityFamily: recent.find((session) => session.workoutType === workoutType)?.activityFamily ??
3948
- "other",
4004
+ workoutTypeLabel: recent.find((session) => session.workoutType === workoutType)
4005
+ ?.workoutTypeLabel ?? workoutType,
4006
+ activityFamily: recent.find((session) => session.workoutType === workoutType)
4007
+ ?.activityFamily ?? "other",
3949
4008
  activityFamilyLabel: recent.find((session) => session.workoutType === workoutType)
3950
4009
  ?.activityFamilyLabel ?? "Other",
3951
4010
  sessionCount: metrics.sessionCount,
@@ -3963,7 +4022,9 @@ function averageNullable(values) {
3963
4022
  }
3964
4023
  function sumNullable(values) {
3965
4024
  const present = values.filter((value) => value != null);
3966
- return present.length > 0 ? present.reduce((sum, value) => sum + value, 0) : null;
4025
+ return present.length > 0
4026
+ ? present.reduce((sum, value) => sum + value, 0)
4027
+ : null;
3967
4028
  }
3968
4029
  function maxNullable(values) {
3969
4030
  const present = values.filter((value) => value != null);
@@ -4040,7 +4101,9 @@ export function getVitalsViewData(userIds) {
4040
4101
  }))
4041
4102
  .filter((value) => value != null);
4042
4103
  const baselineValues = recentValues.slice(Math.max(0, recentValues.length - 8), recentValues.length - 1);
4043
- const baselineValue = baselineValues.length > 0 ? average(baselineValues) : recentValues.at(-2) ?? null;
4104
+ const baselineValue = baselineValues.length > 0
4105
+ ? average(baselineValues)
4106
+ : (recentValues.at(-2) ?? null);
4044
4107
  const latestValue = latestDay
4045
4108
  ? vitalMetricPrimaryValue({
4046
4109
  aggregation: bucket.aggregation,
@@ -4056,9 +4119,13 @@ export function getVitalsViewData(userIds) {
4056
4119
  category: bucket.category,
4057
4120
  unit: bucket.displayUnit,
4058
4121
  aggregation: bucket.aggregation,
4059
- latestValue: latestValue == null ? null : round(latestValue, bucket.aggregation === "cumulative" ? 0 : 1),
4122
+ latestValue: latestValue == null
4123
+ ? null
4124
+ : round(latestValue, bucket.aggregation === "cumulative" ? 0 : 1),
4060
4125
  latestDateKey: latestDay?.dateKey ?? null,
4061
- baselineValue: baselineValue == null ? null : round(baselineValue, bucket.aggregation === "cumulative" ? 0 : 1),
4126
+ baselineValue: baselineValue == null
4127
+ ? null
4128
+ : round(baselineValue, bucket.aggregation === "cumulative" ? 0 : 1),
4062
4129
  deltaValue: latestValue != null && baselineValue != null
4063
4130
  ? round(latestValue - baselineValue, bucket.aggregation === "cumulative" ? 0 : 1)
4064
4131
  : null,
@@ -4072,7 +4139,9 @@ export function getVitalsViewData(userIds) {
4072
4139
  }
4073
4140
  return left.category.localeCompare(right.category);
4074
4141
  });
4075
- const categoryBreakdown = [...new Set(metrics.map((metric) => metric.category))]
4142
+ const categoryBreakdown = [
4143
+ ...new Set(metrics.map((metric) => metric.category))
4144
+ ]
4076
4145
  .map((category) => {
4077
4146
  const categoryMetrics = metrics.filter((metric) => metric.category === category);
4078
4147
  return {
@@ -4087,8 +4156,7 @@ export function getVitalsViewData(userIds) {
4087
4156
  trackedDays: dayCount,
4088
4157
  metricCount: metrics.length,
4089
4158
  latestDateKey: rows[0]?.date_key ?? null,
4090
- latestMetricCount: metrics.filter((metric) => metric.latestDateKey === (rows[0]?.date_key ?? null))
4091
- .length,
4159
+ latestMetricCount: metrics.filter((metric) => metric.latestDateKey === (rows[0]?.date_key ?? null)).length,
4092
4160
  categoryBreakdown
4093
4161
  },
4094
4162
  metrics
@@ -4140,7 +4208,8 @@ export function createSleepSession(input, activity) {
4140
4208
  provenance_json, derived_json, created_at, updated_at
4141
4209
  )
4142
4210
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
4143
- .run(id, externalUid, null, userId, parsed.source, parsed.sourceType, parsed.sourceDevice, sourceTimezone, parsed.localDateKey ?? localDateKeyForTimezone(parsed.endedAt, sourceTimezone), parsed.startedAt, parsed.endedAt, timeInBedSeconds, asleepSeconds, awakeSeconds, sleepScore, timingMetrics.regularityScore, timingMetrics.bedtimeConsistencyMinutes, timingMetrics.wakeConsistencyMinutes, parsed.rawSegmentCount, JSON.stringify(parsed.stageBreakdown), JSON.stringify(parsed.recoveryMetrics), JSON.stringify(parsed.sourceMetrics), JSON.stringify(parsed.links), JSON.stringify(annotations), JSON.stringify({
4211
+ .run(id, externalUid, null, userId, parsed.source, parsed.sourceType, parsed.sourceDevice, sourceTimezone, parsed.localDateKey ??
4212
+ localDateKeyForTimezone(parsed.endedAt, sourceTimezone), parsed.startedAt, parsed.endedAt, timeInBedSeconds, asleepSeconds, awakeSeconds, sleepScore, timingMetrics.regularityScore, timingMetrics.bedtimeConsistencyMinutes, timingMetrics.wakeConsistencyMinutes, parsed.rawSegmentCount, JSON.stringify(parsed.stageBreakdown), JSON.stringify(parsed.recoveryMetrics), JSON.stringify(parsed.sourceMetrics), JSON.stringify(parsed.links), JSON.stringify(annotations), JSON.stringify({
4144
4213
  manualEntry: true,
4145
4214
  entryMode: "local",
4146
4215
  source: parsed.source,
@@ -4150,7 +4219,8 @@ export function createSleepSession(input, activity) {
4150
4219
  createdAt: now,
4151
4220
  ...parsed.provenance
4152
4221
  }), JSON.stringify(derived), now, now);
4153
- summarizeUserHealthDay(userId, parsed.localDateKey ?? localDateKeyForTimezone(parsed.endedAt, sourceTimezone));
4222
+ summarizeUserHealthDay(userId, parsed.localDateKey ??
4223
+ localDateKeyForTimezone(parsed.endedAt, sourceTimezone));
4154
4224
  recordActivityEvent({
4155
4225
  entityType: "sleep_session",
4156
4226
  entityId: id,
@@ -4367,9 +4437,7 @@ export function updateWorkoutSession(workoutId, patch, activity) {
4367
4437
  ? durationSecondsBetween(startedAt, endedAt)
4368
4438
  : current.duration_seconds;
4369
4439
  const currentAnnotations = safeJsonParse(current.annotations_json, {});
4370
- const tags = parsed.tags ??
4371
- safeJsonParse(current.tags_json, []) ??
4372
- [];
4440
+ const tags = parsed.tags ?? safeJsonParse(current.tags_json, []) ?? [];
4373
4441
  const links = parsed.links ??
4374
4442
  safeJsonParse(current.links_json, []);
4375
4443
  const annotations = {
@@ -4505,7 +4573,9 @@ export function updateWorkoutMetadata(workoutId, patch, activity) {
4505
4573
  ...(parsed.subjectiveEffort !== undefined
4506
4574
  ? { subjectiveEffort: parsed.subjectiveEffort }
4507
4575
  : {}),
4508
- ...(parsed.moodBefore !== undefined ? { moodBefore: parsed.moodBefore } : {}),
4576
+ ...(parsed.moodBefore !== undefined
4577
+ ? { moodBefore: parsed.moodBefore }
4578
+ : {}),
4509
4579
  ...(parsed.moodAfter !== undefined ? { moodAfter: parsed.moodAfter } : {}),
4510
4580
  ...(parsed.meaningText !== undefined
4511
4581
  ? { meaningText: parsed.meaningText }
@@ -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.82",
5
+ "version": "0.2.84",
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.82",
3
+ "version": "0.2.84",
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",