forge-openclaw-plugin 0.2.71 → 0.2.72
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.
|
@@ -7620,7 +7620,7 @@ export async function buildServer(options = {}) {
|
|
|
7620
7620
|
app.post("/api/v1/mobile/healthkit/sync-sessions", async (request) => ({
|
|
7621
7621
|
upload: startMobileHealthSyncSession(mobileHealthSyncSessionStartSchema.parse(request.body ?? {}))
|
|
7622
7622
|
}));
|
|
7623
|
-
app.post("/api/v1/mobile/healthkit/sync-sessions/:id/chunks", { bodyLimit:
|
|
7623
|
+
app.post("/api/v1/mobile/healthkit/sync-sessions/:id/chunks", { bodyLimit: 1_600_000 }, async (request) => {
|
|
7624
7624
|
const { id } = request.params;
|
|
7625
7625
|
const rawPayloadJson = JSON.stringify((request.body ?? {}).payload ?? {});
|
|
7626
7626
|
return {
|
|
@@ -339,6 +339,7 @@ export const mobileHealthSyncChunkSchema = z.object({
|
|
|
339
339
|
recordCount: z.number().int().nonnegative().default(0),
|
|
340
340
|
byteCount: z.number().int().nonnegative().default(0),
|
|
341
341
|
checksumSha256: z.string().trim().min(1),
|
|
342
|
+
payloadJsonBase64: z.string().trim().min(1).optional(),
|
|
342
343
|
payload: mobileHealthSyncChunkPayloadSchema.default({})
|
|
343
344
|
});
|
|
344
345
|
export const mobileHealthSyncSessionCompleteSchema = z.object({
|
|
@@ -2685,6 +2686,58 @@ function chunkPayloadJson(payload) {
|
|
|
2685
2686
|
function chunkPayloadChecksum(payloadJson) {
|
|
2686
2687
|
return createHash("sha256").update(payloadJson).digest("hex");
|
|
2687
2688
|
}
|
|
2689
|
+
function parseBase64ChunkPayload(payloadJsonBase64, context) {
|
|
2690
|
+
const compactBase64 = payloadJsonBase64.replace(/\s/g, "");
|
|
2691
|
+
if (compactBase64.length === 0 ||
|
|
2692
|
+
compactBase64.length % 4 === 1 ||
|
|
2693
|
+
!/^[A-Za-z0-9+/]*={0,2}$/.test(compactBase64)) {
|
|
2694
|
+
throw new HttpError(400, "invalid_chunk_payload", "The HealthKit sync chunk payload encoding is invalid.", { mode: "payload_json_base64" });
|
|
2695
|
+
}
|
|
2696
|
+
const decoded = Buffer.from(compactBase64, "base64");
|
|
2697
|
+
const recoded = decoded.toString("base64").replace(/=+$/g, "");
|
|
2698
|
+
if (recoded !== compactBase64.replace(/=+$/g, "")) {
|
|
2699
|
+
throw new HttpError(400, "invalid_chunk_payload", "The HealthKit sync chunk payload encoding is invalid.", { mode: "payload_json_base64" });
|
|
2700
|
+
}
|
|
2701
|
+
const payloadJson = decoded.toString("utf8");
|
|
2702
|
+
let rawPayload;
|
|
2703
|
+
try {
|
|
2704
|
+
rawPayload = JSON.parse(payloadJson);
|
|
2705
|
+
}
|
|
2706
|
+
catch (error) {
|
|
2707
|
+
console.warn("[healthkit-sync] invalid chunk JSON payload", {
|
|
2708
|
+
syncSessionId: context.syncSessionId,
|
|
2709
|
+
chunkId: context.chunkId,
|
|
2710
|
+
family: context.family,
|
|
2711
|
+
mode: "payload_json_base64",
|
|
2712
|
+
bytes: decoded.length,
|
|
2713
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2714
|
+
});
|
|
2715
|
+
throw new HttpError(400, "invalid_chunk_payload", "The HealthKit sync chunk payload is not valid JSON.", { mode: "payload_json_base64" });
|
|
2716
|
+
}
|
|
2717
|
+
const parsedPayload = mobileHealthSyncChunkPayloadSchema.parse(rawPayload);
|
|
2718
|
+
return {
|
|
2719
|
+
payload: parsedPayload,
|
|
2720
|
+
payloadJson,
|
|
2721
|
+
byteCount: decoded.length,
|
|
2722
|
+
mode: "payload_json_base64"
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function resolveChunkWirePayload(parsed, syncSessionId, rawPayloadJson) {
|
|
2726
|
+
if (parsed.payloadJsonBase64) {
|
|
2727
|
+
return parseBase64ChunkPayload(parsed.payloadJsonBase64, {
|
|
2728
|
+
syncSessionId,
|
|
2729
|
+
chunkId: parsed.chunkId,
|
|
2730
|
+
family: parsed.family
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
const payloadJson = rawPayloadJson ?? chunkPayloadJson(parsed.payload);
|
|
2734
|
+
return {
|
|
2735
|
+
payload: parsed.payload,
|
|
2736
|
+
payloadJson,
|
|
2737
|
+
byteCount: Buffer.byteLength(payloadJson, "utf8"),
|
|
2738
|
+
mode: "legacy_payload_object"
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2688
2741
|
function summarizeChunkPayload(family, payload) {
|
|
2689
2742
|
switch (family) {
|
|
2690
2743
|
case "sleep_nights":
|
|
@@ -2834,18 +2887,28 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
|
|
|
2834
2887
|
progress
|
|
2835
2888
|
};
|
|
2836
2889
|
}
|
|
2837
|
-
const
|
|
2838
|
-
const
|
|
2839
|
-
const actualByteCount =
|
|
2890
|
+
const wirePayload = resolveChunkWirePayload(parsed, syncSessionId, rawPayloadJson);
|
|
2891
|
+
const payloadJson = wirePayload.payloadJson;
|
|
2892
|
+
const actualByteCount = wirePayload.byteCount;
|
|
2840
2893
|
if (actualByteCount > HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES) {
|
|
2841
2894
|
throw new HttpError(413, "chunk_too_large", "The HealthKit sync chunk is too large.", {
|
|
2842
2895
|
maxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
|
|
2843
2896
|
actualBytes: actualByteCount
|
|
2844
2897
|
});
|
|
2845
2898
|
}
|
|
2846
|
-
const serverChecksum = chunkPayloadChecksum(
|
|
2899
|
+
const serverChecksum = chunkPayloadChecksum(payloadJson);
|
|
2847
2900
|
if (parsed.checksumSha256 !== serverChecksum) {
|
|
2848
|
-
|
|
2901
|
+
console.warn("[healthkit-sync] chunk checksum mismatch", {
|
|
2902
|
+
syncSessionId,
|
|
2903
|
+
chunkId: parsed.chunkId,
|
|
2904
|
+
family: parsed.family,
|
|
2905
|
+
mode: wirePayload.mode,
|
|
2906
|
+
clientChecksum: parsed.checksumSha256.slice(0, 12),
|
|
2907
|
+
serverChecksum: serverChecksum.slice(0, 12),
|
|
2908
|
+
clientByteCount: parsed.byteCount,
|
|
2909
|
+
actualByteCount
|
|
2910
|
+
});
|
|
2911
|
+
throw new HttpError(409, "chunk_checksum_mismatch", "The HealthKit sync chunk checksum does not match its payload.", { actualBytes: actualByteCount, mode: wirePayload.mode });
|
|
2849
2912
|
}
|
|
2850
2913
|
const now = nowIso();
|
|
2851
2914
|
getDatabase()
|
|
@@ -2856,10 +2919,11 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
|
|
|
2856
2919
|
)
|
|
2857
2920
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
2858
2921
|
.run(mobileSyncChunkRecordId(), syncSessionId, parsed.chunkId, parsed.sequence, parsed.family, serverChecksum, parsed.recordCount, parsed.byteCount || actualByteCount, payloadJson, JSON.stringify({
|
|
2859
|
-
...summarizeChunkPayload(parsed.family,
|
|
2922
|
+
...summarizeChunkPayload(parsed.family, wirePayload.payload),
|
|
2860
2923
|
clientByteCount: parsed.byteCount,
|
|
2861
2924
|
actualByteCount,
|
|
2862
|
-
serverChecksum
|
|
2925
|
+
serverChecksum,
|
|
2926
|
+
mode: wirePayload.mode
|
|
2863
2927
|
}), now, now, now, now);
|
|
2864
2928
|
const progress = updateMobileSyncSessionProgress(syncSessionId);
|
|
2865
2929
|
return {
|
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.72",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED