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-
|
|
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({}),
|
|
@@ -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
|
|
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
|
|
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 ??
|
|
539
|
-
const nextAppliedEnabled = patch.appliedEnabled ??
|
|
540
|
-
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;
|
|
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
|
|
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 ===
|
|
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
|
|
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
|
-
?
|
|
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
|
|
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
|
-
?
|
|
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
|
|
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 ||
|
|
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 ||
|
|
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 ??
|
|
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 ??
|
|
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 ||
|
|
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
|
-
...
|
|
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" &&
|
|
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)
|
|
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
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
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" &&
|
|
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 &&
|
|
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 +
|
|
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 +
|
|
3586
|
-
|
|
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" &&
|
|
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)
|
|
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)
|
|
3775
|
-
|
|
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)
|
|
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:
|
|
3946
|
-
|
|
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)
|
|
3953
|
-
workoutType,
|
|
3954
|
-
activityFamily: recent.find((session) => session.workoutType === workoutType)
|
|
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
|
|
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
|
|
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
|
|
4122
|
+
latestValue: latestValue == null
|
|
4123
|
+
? null
|
|
4124
|
+
: round(latestValue, bucket.aggregation === "cumulative" ? 0 : 1),
|
|
4067
4125
|
latestDateKey: latestDay?.dateKey ?? null,
|
|
4068
|
-
baselineValue: baselineValue == null
|
|
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 = [
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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 }
|
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