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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ??
|
|
538
|
-
const nextAppliedEnabled = patch.appliedEnabled ??
|
|
539
|
-
const nextSyncEligible = patch.syncEligible ??
|
|
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
|
|
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 ===
|
|
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
|
|
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
|
-
?
|
|
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
|
|
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
|
-
?
|
|
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
|
|
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 ||
|
|
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 ||
|
|
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 ??
|
|
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 ??
|
|
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 ||
|
|
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
|
-
...
|
|
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" &&
|
|
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)
|
|
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
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
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" &&
|
|
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 &&
|
|
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 +
|
|
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 +
|
|
3579
|
-
|
|
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" &&
|
|
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)
|
|
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)
|
|
3768
|
-
|
|
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)
|
|
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:
|
|
3939
|
-
|
|
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)
|
|
3946
|
-
workoutType,
|
|
3947
|
-
activityFamily: recent.find((session) => session.workoutType === workoutType)
|
|
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
|
|
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
|
|
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
|
|
4122
|
+
latestValue: latestValue == null
|
|
4123
|
+
? null
|
|
4124
|
+
: round(latestValue, bucket.aggregation === "cumulative" ? 0 : 1),
|
|
4060
4125
|
latestDateKey: latestDay?.dateKey ?? null,
|
|
4061
|
-
baselineValue: baselineValue == null
|
|
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 = [
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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 }
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.2.84",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED