forge-openclaw-plugin 0.2.83 → 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({}),
@@ -294,14 +308,13 @@ export const mobileHealthSyncSessionStartSchema = mobileHealthSyncSchema
294
308
  sourceStates: true
295
309
  })
296
310
  .extend({
297
- schemaVersion: z
298
- .string()
299
- .trim()
300
- .default(HEALTH_MOBILE_SYNC_SCHEMA_VERSION),
311
+ schemaVersion: z.string().trim().default(HEALTH_MOBILE_SYNC_SCHEMA_VERSION),
301
312
  requestedFamilies: z
302
313
  .array(mobileHealthSyncFamilySchema)
303
314
  .default(defaultMobileHealthSyncFamilies),
304
- expectedCounts: z.record(z.string(), z.number().int().nonnegative()).default({}),
315
+ expectedCounts: z
316
+ .record(z.string(), z.number().int().nonnegative())
317
+ .default({}),
305
318
  metadata: z.record(z.string(), z.unknown()).default({})
306
319
  });
307
320
  const workoutTimeSeriesChunkPayloadSchema = z.object({
@@ -355,7 +368,9 @@ export const mobileHealthSyncChunkSchema = z.object({
355
368
  });
356
369
  export const mobileHealthSyncSessionCompleteSchema = z.object({
357
370
  finalCursor: z.record(z.string(), z.unknown()).default({}),
358
- expectedCounts: z.record(z.string(), z.number().int().nonnegative()).default({})
371
+ expectedCounts: z
372
+ .record(z.string(), z.number().int().nonnegative())
373
+ .default({})
359
374
  });
360
375
  export const verifyCompanionPairingSchema = z.object({
361
376
  sessionId: z.string().trim().min(1),
@@ -535,9 +550,9 @@ function upsertPairingSourceState(pairing, source, patch) {
535
550
  ...patch.metadata
536
551
  }
537
552
  : safeJsonParse(current.metadata_json, {});
538
- const nextDesiredEnabled = patch.desiredEnabled ?? (current.desired_enabled === 1);
539
- const nextAppliedEnabled = patch.appliedEnabled ?? (current.applied_enabled === 1);
540
- 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;
541
556
  const nextUpdatedAt = nowIso();
542
557
  getDatabase()
543
558
  .prepare(`UPDATE companion_pairing_source_states
@@ -681,9 +696,7 @@ function computeSleepDerivedMetrics(input) {
681
696
  })
682
697
  .reduce((total, stage) => total + stage.seconds, 0);
683
698
  const restorativeShare = input.asleepSeconds > 0 ? restorativeSeconds / input.asleepSeconds : 0;
684
- const sleepDebtHours = input.asleepSeconds > 0
685
- ? Math.max(0, 8 - input.asleepSeconds / 3600)
686
- : 8;
699
+ const sleepDebtHours = input.asleepSeconds > 0 ? Math.max(0, 8 - input.asleepSeconds / 3600) : 8;
687
700
  return {
688
701
  durationHours: round(input.asleepSeconds / 3600, 2),
689
702
  efficiency: round(efficiency, 3),
@@ -1113,7 +1126,10 @@ function inferHistoricalRawStage(payload) {
1113
1126
  return "asleep_unspecified";
1114
1127
  }
1115
1128
  function inferHistoricalSleepBucket(stage) {
1116
- const normalized = stage.trim().toLowerCase().replace(/[\s-]+/g, "_");
1129
+ const normalized = stage
1130
+ .trim()
1131
+ .toLowerCase()
1132
+ .replace(/[\s-]+/g, "_");
1117
1133
  if (normalized.includes("bed")) {
1118
1134
  return "in_bed";
1119
1135
  }
@@ -1155,22 +1171,24 @@ function sleepHasReflection(session) {
1155
1171
  tags.length > 0);
1156
1172
  }
1157
1173
  function sleepEfficiency(session) {
1158
- return typeof session.derived.efficiency === "number"
1174
+ return typeof session.derived.efficiency ===
1175
+ "number"
1159
1176
  ? session.derived.efficiency
1160
1177
  : session.timeInBedSeconds > 0
1161
1178
  ? session.asleepSeconds / session.timeInBedSeconds
1162
1179
  : 0;
1163
1180
  }
1164
1181
  function sleepRestorativeShare(session) {
1165
- return typeof session.derived.restorativeShare ===
1166
- "number"
1182
+ return typeof session.derived
1183
+ .restorativeShare === "number"
1167
1184
  ? session.derived.restorativeShare
1168
1185
  : 0;
1169
1186
  }
1170
1187
  function sleepRecoveryState(session) {
1171
1188
  return typeof session.derived.recoveryState ===
1172
1189
  "string"
1173
- ? (session.derived.recoveryState || null)
1190
+ ? session.derived.recoveryState ||
1191
+ null
1174
1192
  : null;
1175
1193
  }
1176
1194
  function sleepQualitativeState(session) {
@@ -1211,7 +1229,9 @@ function sleepStageShare(session) {
1211
1229
  return session.stageBreakdown.map((stage) => ({
1212
1230
  stage: stage.stage,
1213
1231
  seconds: stage.seconds,
1214
- 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
1215
1235
  }));
1216
1236
  }
1217
1237
  function buildSleepSurfaceNight(session, baselineAverageSleepSeconds) {
@@ -1239,8 +1259,8 @@ function buildSleepSurfaceNight(session, baselineAverageSleepSeconds) {
1239
1259
  hasRawSegments: session.rawSegmentCount > 0,
1240
1260
  qualitySummary: typeof session.annotations.qualitySummary ===
1241
1261
  "string"
1242
- ? (session.annotations.qualitySummary ||
1243
- null)
1262
+ ? session.annotations
1263
+ .qualitySummary || null
1244
1264
  : null,
1245
1265
  stageBreakdown: sleepStageShare(session)
1246
1266
  };
@@ -1289,7 +1309,10 @@ function pickDisplaySleepSessions(sessions) {
1289
1309
  return [...byDateKey.values()].sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));
1290
1310
  }
1291
1311
  function normalizeTimelineStage(stage, bucket) {
1292
- const normalized = stage.trim().toLowerCase().replace(/[\s-]+/g, "_");
1312
+ const normalized = stage
1313
+ .trim()
1314
+ .toLowerCase()
1315
+ .replace(/[\s-]+/g, "_");
1293
1316
  if (bucket === "in_bed" || normalized.includes("bed")) {
1294
1317
  return "in_bed";
1295
1318
  }
@@ -1560,8 +1583,7 @@ export function revokeCompanionPairingSession(pairingSessionId, activity) {
1560
1583
  }
1561
1584
  export function revokeAllCompanionPairingSessions(input, activity) {
1562
1585
  const parsed = revokeAllCompanionPairingSessionsSchema.parse(input ?? {});
1563
- const rows = listPairingRows(parsed.userIds.length > 0 ? parsed.userIds : undefined)
1564
- .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");
1565
1587
  const sessions = revokePairingRows(rows, {
1566
1588
  actor: activity?.actor ?? null,
1567
1589
  source: activity?.source ?? "ui",
@@ -1794,7 +1816,8 @@ function normalizeSleepSegmentInput(input) {
1794
1816
  return {
1795
1817
  ...input,
1796
1818
  sourceTimezone,
1797
- localDateKey: input.localDateKey || localDateKeyForTimezone(input.endedAt, sourceTimezone)
1819
+ localDateKey: input.localDateKey ||
1820
+ localDateKeyForTimezone(input.endedAt, sourceTimezone)
1798
1821
  };
1799
1822
  }
1800
1823
  function normalizeSleepRawRecordInput(input) {
@@ -1802,7 +1825,8 @@ function normalizeSleepRawRecordInput(input) {
1802
1825
  return {
1803
1826
  ...input,
1804
1827
  sourceTimezone,
1805
- localDateKey: input.localDateKey || localDateKeyForTimezone(input.endedAt, sourceTimezone)
1828
+ localDateKey: input.localDateKey ||
1829
+ localDateKeyForTimezone(input.endedAt, sourceTimezone)
1806
1830
  };
1807
1831
  }
1808
1832
  function listNormalizedSleepNights(payload) {
@@ -2018,8 +2042,7 @@ function upsertVitalDaySummary(userId, input) {
2018
2042
  }
2019
2043
  function summarizeUserHealthDay(userId, dateKeyValue) {
2020
2044
  const sleeps = listSleepRows([userId]).filter((row) => sleepSessionDateKey(row) === dateKeyValue ||
2021
- localDateKeyForTimezone(row.started_at, resolveTimeZone(row.source_timezone)) ===
2022
- dateKeyValue);
2045
+ localDateKeyForTimezone(row.started_at, resolveTimeZone(row.source_timezone)) === dateKeyValue);
2023
2046
  const workouts = listWorkoutRows([userId]).filter((row) => dayKey(row.started_at) === dateKeyValue);
2024
2047
  const totalSleepSeconds = sleeps.reduce((sum, row) => sum + row.asleep_seconds, 0);
2025
2048
  const totalWorkoutSeconds = workouts.reduce((sum, row) => sum + row.duration_seconds, 0);
@@ -2147,12 +2170,16 @@ function insertOrUpdateWorkoutSession(pairing, input) {
2147
2170
  (typeof existingProvenance.sourceProductType === "string"
2148
2171
  ? existingProvenance.sourceProductType
2149
2172
  : null),
2150
- activity: input.activity ?? existingDerived.activity ?? existingProvenance.activity,
2173
+ activity: input.activity ??
2174
+ existingDerived.activity ??
2175
+ existingProvenance.activity,
2151
2176
  details: input.details ?? existingDerived.details ?? existingProvenance.details
2152
2177
  });
2153
2178
  const mergedLinks = mergeHealthLinks(existingLinks, input.links, annotations.links);
2154
2179
  const mergedTags = mergeStringLists(existingTags, annotations.tags);
2155
- const nextSubjectiveEffort = matchedGenerated.subjective_effort ?? annotations.subjectiveEffort ?? null;
2180
+ const nextSubjectiveEffort = matchedGenerated.subjective_effort ??
2181
+ annotations.subjectiveEffort ??
2182
+ null;
2156
2183
  const nextMoodBefore = matchedGenerated.mood_before || annotations.moodBefore;
2157
2184
  const nextMoodAfter = matchedGenerated.mood_after || annotations.moodAfter;
2158
2185
  const nextMeaningText = matchedGenerated.meaning_text || annotations.meaningText;
@@ -2201,7 +2228,8 @@ function insertOrUpdateWorkoutSession(pairing, input) {
2201
2228
  }), matchedGenerated.generated_from_habit_id ? "merged" : "standalone", now, matchedGenerated.id);
2202
2229
  persistWorkoutEvidenceForInput(matchedGenerated.id, pairing.user_id, input);
2203
2230
  return {
2204
- mode: matchedGenerated.generated_from_habit_id || matchedGenerated.source !== "apple_health"
2231
+ mode: matchedGenerated.generated_from_habit_id ||
2232
+ matchedGenerated.source !== "apple_health"
2205
2233
  ? "merged"
2206
2234
  : "updated",
2207
2235
  id: matchedGenerated.id
@@ -2364,13 +2392,14 @@ function backfillHistoricalSleepEvidence() {
2364
2392
  )
2365
2393
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
2366
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({
2367
- ...(safeJsonParse(rawLog.metadata_json, {})),
2395
+ ...safeJsonParse(rawLog.metadata_json, {}),
2368
2396
  migratedFromRawLogId: rawLog.id
2369
2397
  }), rawLog.created_at);
2370
2398
  }
2371
2399
  }
2372
2400
  if (existingSegmentCount.count === 0 && rawLogs.length > 0) {
2373
- 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"));
2374
2403
  for (const rawLog of authoritativeLogs) {
2375
2404
  const payload = safeJsonParse(rawLog.payload_json, {});
2376
2405
  const inferredStage = inferHistoricalRawStage(payload);
@@ -2499,7 +2528,9 @@ function ensureLegacyAppleSleepHistoryRepaired() {
2499
2528
  for (const cluster of clusters) {
2500
2529
  const rowsForNight = selectAuthoritativeSleepRows(cluster);
2501
2530
  const startedAt = rowsForNight[0].started_at;
2502
- 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);
2503
2534
  const sourceTimezone = inferHistoricalSleepTimeZone(cluster.map((row) => row.source_timezone));
2504
2535
  const localDateKey = localDateKeyForTimezone(endedAt, sourceTimezone);
2505
2536
  const asleepSeconds = unionDurationSeconds(rowsForNight.map((row) => ({
@@ -2999,24 +3030,34 @@ export function startMobileHealthSyncSession(payload) {
2999
3030
  if (existing &&
3000
3031
  existing.pairing_session_id === pairing.id &&
3001
3032
  existing.status === "running") {
3002
- const receivedChunkIds = getDatabase()
3003
- .prepare(`SELECT chunk_id
3004
- FROM health_mobile_sync_chunks
3005
- WHERE sync_session_id = ?
3006
- ORDER BY sequence ASC`)
3007
- .all(resumeSyncSessionId)
3008
- .map((row) => row.chunk_id);
3009
- return {
3010
- syncSessionId: resumeSyncSessionId,
3011
- schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
3012
- chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
3013
- chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
3014
- chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
3015
- acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
3016
- supportsCompression: true,
3017
- acceptedFamilies: safeJsonParse(existing.requested_families_json, parsed.requestedFamilies),
3018
- receivedChunkIds
3019
- };
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);
3020
3061
  }
3021
3062
  }
3022
3063
  const now = nowIso();
@@ -3082,7 +3123,8 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
3082
3123
  actualBytes: actualByteCount
3083
3124
  });
3084
3125
  }
3085
- if (wirePayload.mode !== "legacy_payload_object" && parsed.byteCount !== actualByteCount) {
3126
+ if (wirePayload.mode !== "legacy_payload_object" &&
3127
+ parsed.byteCount !== actualByteCount) {
3086
3128
  console.warn("[healthkit-sync] chunk byte count mismatch", {
3087
3129
  syncSessionId,
3088
3130
  chunkId: parsed.chunkId,
@@ -3478,7 +3520,8 @@ export function ingestMobileHealthSync(payload) {
3478
3520
  for (const sleep of normalizedSleepNights) {
3479
3521
  const targetSessionId = sleepSessionsByLocalDate
3480
3522
  .get(sleep.localDateKey)
3481
- ?.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;
3482
3525
  if (targetSessionId) {
3483
3526
  replaceHistoricalSleepSessionsForDate(pairing.user_id, sleep.localDateKey, targetSessionId);
3484
3527
  }
@@ -3566,7 +3609,9 @@ export function ingestMobileHealthSync(payload) {
3566
3609
  screenTimeDaySummaries: parsed.screenTime.daySummaries.length,
3567
3610
  screenTimeHourlySegments: parsed.screenTime.hourlySegments.length,
3568
3611
  createdCount: createdCount + movementSync.createdCount,
3569
- updatedCount: updatedCount + movementSync.updatedCount + screenTimeSync.updatedCount,
3612
+ updatedCount: updatedCount +
3613
+ movementSync.updatedCount +
3614
+ screenTimeSync.updatedCount,
3570
3615
  mergedCount
3571
3616
  }
3572
3617
  });
@@ -3582,8 +3627,12 @@ export function ingestMobileHealthSync(payload) {
3582
3627
  workouts: parsed.workouts.length,
3583
3628
  vitalsDaySummaries: parsed.vitals.daySummaries.length,
3584
3629
  vitalsMetricEntries: vitalMetricEntries,
3585
- createdCount: createdCount + movementSync.createdCount + screenTimeSync.createdCount,
3586
- updatedCount: updatedCount + movementSync.updatedCount + screenTimeSync.updatedCount,
3630
+ createdCount: createdCount +
3631
+ movementSync.createdCount +
3632
+ screenTimeSync.createdCount,
3633
+ updatedCount: updatedCount +
3634
+ movementSync.updatedCount +
3635
+ screenTimeSync.updatedCount,
3587
3636
  mergedCount,
3588
3637
  movementStays: parsed.movement.stays.length,
3589
3638
  movementTrips: parsed.movement.trips.length,
@@ -3677,10 +3726,12 @@ export function getCompanionOverview(userIds) {
3677
3726
  return (session.links.length > 0 ||
3678
3727
  (typeof annotations.qualitySummary === "string" &&
3679
3728
  annotations.qualitySummary.length > 0) ||
3680
- (typeof annotations.notes === "string" && annotations.notes.length > 0) ||
3729
+ (typeof annotations.notes === "string" &&
3730
+ annotations.notes.length > 0) ||
3681
3731
  tags.length > 0);
3682
3732
  }).length,
3683
- linkedWorkouts: workouts.filter((session) => session.links.length > 0).length,
3733
+ linkedWorkouts: workouts.filter((session) => session.links.length > 0)
3734
+ .length,
3684
3735
  habitGeneratedWorkouts: workouts.filter((session) => session.sourceType === "habit_generated").length,
3685
3736
  reconciledWorkouts: workouts.filter((session) => session.reconciliationStatus === "merged").length,
3686
3737
  vitalsDaySummaries: vitalsRows.length,
@@ -3740,9 +3791,9 @@ export function getSleepViewData(userIds) {
3740
3791
  ? session.asleepSeconds / session.timeInBedSeconds
3741
3792
  : 0)), 2),
3742
3793
  averageRestorativeShare: round(average(weekly.map((session) => sleepRestorativeShare(session))), 2),
3743
- reflectiveNightCount: weekly.filter((session) => sleepHasReflection(session))
3794
+ reflectiveNightCount: weekly.filter((session) => sleepHasReflection(session)).length,
3795
+ linkedNightCount: weekly.filter((session) => session.links.length > 0)
3744
3796
  .length,
3745
- linkedNightCount: weekly.filter((session) => session.links.length > 0).length,
3746
3797
  averageBedtimeConsistencyMinutes: Math.round(average(weekly
3747
3798
  .map((session) => session.bedtimeConsistencyMinutes)
3748
3799
  .filter((value) => value !== null))),
@@ -3771,8 +3822,10 @@ export function getSleepViewData(userIds) {
3771
3822
  .map((session) => ({
3772
3823
  id: session.id,
3773
3824
  dateKey: session.localDateKey,
3774
- onsetHour: getTimeZoneParts(session.startedAt, session.sourceTimezone).hour,
3775
- 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,
3776
3829
  sleepHours: Number((session.asleepSeconds / 3600).toFixed(2))
3777
3830
  }))
3778
3831
  .reverse(),
@@ -3914,14 +3967,14 @@ export function getFitnessViewData(userIds) {
3914
3967
  averageEffort: round(average(recent
3915
3968
  .map((session) => session.subjectiveEffort)
3916
3969
  .filter((value) => value !== null)), 1),
3917
- linkedSessionCount: recent.filter((session) => session.links.length > 0).length,
3970
+ linkedSessionCount: recent.filter((session) => session.links.length > 0)
3971
+ .length,
3918
3972
  plannedSessionCount: recent.filter((session) => session.plannedContext.trim().length > 0).length,
3919
3973
  importedSessionCount: recent.filter((session) => session.source === "apple_health").length,
3920
3974
  habitGeneratedSessionCount: recent.filter((session) => session.sourceType === "habit_generated").length,
3921
3975
  reconciledSessionCount: recent.filter((session) => session.reconciliationStatus === "merged").length,
3922
3976
  topWorkoutType: orderedWorkoutTypes[0]?.[0] ?? null,
3923
- topWorkoutTypeLabel: recent.find((session) => session.workoutType === orderedWorkoutTypes[0]?.[0])
3924
- ?.workoutTypeLabel ?? null,
3977
+ topWorkoutTypeLabel: recent.find((session) => session.workoutType === orderedWorkoutTypes[0]?.[0])?.workoutTypeLabel ?? null,
3925
3978
  streakDays: Array.from(new Set(weekly.map((session) => dayKey(session.startedAt)))).length,
3926
3979
  averageHeartRateCoverage: heartRateCoverageCount > 0
3927
3980
  ? Number((heartRateCoverageSum / heartRateCoverageCount).toFixed(3))
@@ -3942,17 +3995,16 @@ export function getFitnessViewData(userIds) {
3942
3995
  energyKcal: Math.round(session.totalEnergyKcal ?? session.activeEnergyKcal ?? 0),
3943
3996
  zoneDurations: session.analytics
3944
3997
  ?.zoneDurations ?? [],
3945
- trainingLoad: (session.analytics
3946
- ?.load?.trimp ?? null),
3947
- heartRateCoverage: (session.analytics?.dataQuality?.sampleCoverage ?? 0)
3998
+ trainingLoad: session.analytics?.load?.trimp ?? null,
3999
+ heartRateCoverage: session.analytics?.dataQuality?.sampleCoverage ?? 0
3948
4000
  }))
3949
4001
  .reverse(),
3950
4002
  typeBreakdown: orderedWorkoutTypes.map(([workoutType, metrics]) => ({
3951
4003
  workoutType,
3952
- workoutTypeLabel: recent.find((session) => session.workoutType === workoutType)?.workoutTypeLabel ??
3953
- workoutType,
3954
- activityFamily: recent.find((session) => session.workoutType === workoutType)?.activityFamily ??
3955
- "other",
4004
+ workoutTypeLabel: recent.find((session) => session.workoutType === workoutType)
4005
+ ?.workoutTypeLabel ?? workoutType,
4006
+ activityFamily: recent.find((session) => session.workoutType === workoutType)
4007
+ ?.activityFamily ?? "other",
3956
4008
  activityFamilyLabel: recent.find((session) => session.workoutType === workoutType)
3957
4009
  ?.activityFamilyLabel ?? "Other",
3958
4010
  sessionCount: metrics.sessionCount,
@@ -3970,7 +4022,9 @@ function averageNullable(values) {
3970
4022
  }
3971
4023
  function sumNullable(values) {
3972
4024
  const present = values.filter((value) => value != null);
3973
- 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;
3974
4028
  }
3975
4029
  function maxNullable(values) {
3976
4030
  const present = values.filter((value) => value != null);
@@ -4047,7 +4101,9 @@ export function getVitalsViewData(userIds) {
4047
4101
  }))
4048
4102
  .filter((value) => value != null);
4049
4103
  const baselineValues = recentValues.slice(Math.max(0, recentValues.length - 8), recentValues.length - 1);
4050
- 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);
4051
4107
  const latestValue = latestDay
4052
4108
  ? vitalMetricPrimaryValue({
4053
4109
  aggregation: bucket.aggregation,
@@ -4063,9 +4119,13 @@ export function getVitalsViewData(userIds) {
4063
4119
  category: bucket.category,
4064
4120
  unit: bucket.displayUnit,
4065
4121
  aggregation: bucket.aggregation,
4066
- 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),
4067
4125
  latestDateKey: latestDay?.dateKey ?? null,
4068
- 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),
4069
4129
  deltaValue: latestValue != null && baselineValue != null
4070
4130
  ? round(latestValue - baselineValue, bucket.aggregation === "cumulative" ? 0 : 1)
4071
4131
  : null,
@@ -4079,7 +4139,9 @@ export function getVitalsViewData(userIds) {
4079
4139
  }
4080
4140
  return left.category.localeCompare(right.category);
4081
4141
  });
4082
- const categoryBreakdown = [...new Set(metrics.map((metric) => metric.category))]
4142
+ const categoryBreakdown = [
4143
+ ...new Set(metrics.map((metric) => metric.category))
4144
+ ]
4083
4145
  .map((category) => {
4084
4146
  const categoryMetrics = metrics.filter((metric) => metric.category === category);
4085
4147
  return {
@@ -4094,8 +4156,7 @@ export function getVitalsViewData(userIds) {
4094
4156
  trackedDays: dayCount,
4095
4157
  metricCount: metrics.length,
4096
4158
  latestDateKey: rows[0]?.date_key ?? null,
4097
- latestMetricCount: metrics.filter((metric) => metric.latestDateKey === (rows[0]?.date_key ?? null))
4098
- .length,
4159
+ latestMetricCount: metrics.filter((metric) => metric.latestDateKey === (rows[0]?.date_key ?? null)).length,
4099
4160
  categoryBreakdown
4100
4161
  },
4101
4162
  metrics
@@ -4147,7 +4208,8 @@ export function createSleepSession(input, activity) {
4147
4208
  provenance_json, derived_json, created_at, updated_at
4148
4209
  )
4149
4210
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
4150
- .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({
4151
4213
  manualEntry: true,
4152
4214
  entryMode: "local",
4153
4215
  source: parsed.source,
@@ -4157,7 +4219,8 @@ export function createSleepSession(input, activity) {
4157
4219
  createdAt: now,
4158
4220
  ...parsed.provenance
4159
4221
  }), JSON.stringify(derived), now, now);
4160
- summarizeUserHealthDay(userId, parsed.localDateKey ?? localDateKeyForTimezone(parsed.endedAt, sourceTimezone));
4222
+ summarizeUserHealthDay(userId, parsed.localDateKey ??
4223
+ localDateKeyForTimezone(parsed.endedAt, sourceTimezone));
4161
4224
  recordActivityEvent({
4162
4225
  entityType: "sleep_session",
4163
4226
  entityId: id,
@@ -4374,9 +4437,7 @@ export function updateWorkoutSession(workoutId, patch, activity) {
4374
4437
  ? durationSecondsBetween(startedAt, endedAt)
4375
4438
  : current.duration_seconds;
4376
4439
  const currentAnnotations = safeJsonParse(current.annotations_json, {});
4377
- const tags = parsed.tags ??
4378
- safeJsonParse(current.tags_json, []) ??
4379
- [];
4440
+ const tags = parsed.tags ?? safeJsonParse(current.tags_json, []) ?? [];
4380
4441
  const links = parsed.links ??
4381
4442
  safeJsonParse(current.links_json, []);
4382
4443
  const annotations = {
@@ -4512,7 +4573,9 @@ export function updateWorkoutMetadata(workoutId, patch, activity) {
4512
4573
  ...(parsed.subjectiveEffort !== undefined
4513
4574
  ? { subjectiveEffort: parsed.subjectiveEffort }
4514
4575
  : {}),
4515
- ...(parsed.moodBefore !== undefined ? { moodBefore: parsed.moodBefore } : {}),
4576
+ ...(parsed.moodBefore !== undefined
4577
+ ? { moodBefore: parsed.moodBefore }
4578
+ : {}),
4516
4579
  ...(parsed.moodAfter !== undefined ? { moodAfter: parsed.moodAfter } : {}),
4517
4580
  ...(parsed.meaningText !== undefined
4518
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.83",
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.83",
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",