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: 1_250_000 }, async (request) => {
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 payloadJson = chunkPayloadJson(parsed.payload);
2838
- const checksumPayloadJson = rawPayloadJson ?? payloadJson;
2839
- const actualByteCount = Buffer.byteLength(checksumPayloadJson, "utf8");
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(checksumPayloadJson);
2899
+ const serverChecksum = chunkPayloadChecksum(payloadJson);
2847
2900
  if (parsed.checksumSha256 !== serverChecksum) {
2848
- throw new HttpError(409, "chunk_checksum_mismatch", "The HealthKit sync chunk checksum does not match its payload.", { actualBytes: actualByteCount });
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, parsed.payload),
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 {
@@ -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.71",
5
+ "version": "0.2.72",
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.71",
3
+ "version": "0.2.72",
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",