forge-openclaw-plugin 0.2.71 → 0.2.73

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 {
@@ -262,6 +262,11 @@ export const mobileHealthSyncSchema = z.object({
262
262
  const HEALTH_MOBILE_SYNC_SCHEMA_VERSION = "healthkit-sync-v2";
263
263
  const HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES = 512_000;
264
264
  const HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES = 1_000_000;
265
+ const HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING = "payload_json_base64";
266
+ const HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS = [
267
+ HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
268
+ "legacy_payload_object"
269
+ ];
265
270
  const HEALTH_MOBILE_SYNC_SESSION_TTL_MS = 1000 * 60 * 60 * 24;
266
271
  const mobileHealthSyncFamilySchema = z.enum([
267
272
  "sleep_nights",
@@ -339,6 +344,7 @@ export const mobileHealthSyncChunkSchema = z.object({
339
344
  recordCount: z.number().int().nonnegative().default(0),
340
345
  byteCount: z.number().int().nonnegative().default(0),
341
346
  checksumSha256: z.string().trim().min(1),
347
+ payloadJsonBase64: z.string().trim().min(1).optional(),
342
348
  payload: mobileHealthSyncChunkPayloadSchema.default({})
343
349
  });
344
350
  export const mobileHealthSyncSessionCompleteSchema = z.object({
@@ -2685,6 +2691,58 @@ function chunkPayloadJson(payload) {
2685
2691
  function chunkPayloadChecksum(payloadJson) {
2686
2692
  return createHash("sha256").update(payloadJson).digest("hex");
2687
2693
  }
2694
+ function parseBase64ChunkPayload(payloadJsonBase64, context) {
2695
+ const compactBase64 = payloadJsonBase64.replace(/\s/g, "");
2696
+ if (compactBase64.length === 0 ||
2697
+ compactBase64.length % 4 === 1 ||
2698
+ !/^[A-Za-z0-9+/]*={0,2}$/.test(compactBase64)) {
2699
+ throw new HttpError(400, "invalid_chunk_payload", "The HealthKit sync chunk payload encoding is invalid.", { mode: "payload_json_base64" });
2700
+ }
2701
+ const decoded = Buffer.from(compactBase64, "base64");
2702
+ const recoded = decoded.toString("base64").replace(/=+$/g, "");
2703
+ if (recoded !== compactBase64.replace(/=+$/g, "")) {
2704
+ throw new HttpError(400, "invalid_chunk_payload", "The HealthKit sync chunk payload encoding is invalid.", { mode: "payload_json_base64" });
2705
+ }
2706
+ const payloadJson = decoded.toString("utf8");
2707
+ let rawPayload;
2708
+ try {
2709
+ rawPayload = JSON.parse(payloadJson);
2710
+ }
2711
+ catch (error) {
2712
+ console.warn("[healthkit-sync] invalid chunk JSON payload", {
2713
+ syncSessionId: context.syncSessionId,
2714
+ chunkId: context.chunkId,
2715
+ family: context.family,
2716
+ mode: "payload_json_base64",
2717
+ bytes: decoded.length,
2718
+ error: error instanceof Error ? error.message : String(error)
2719
+ });
2720
+ throw new HttpError(400, "invalid_chunk_payload", "The HealthKit sync chunk payload is not valid JSON.", { mode: "payload_json_base64" });
2721
+ }
2722
+ const parsedPayload = mobileHealthSyncChunkPayloadSchema.parse(rawPayload);
2723
+ return {
2724
+ payload: parsedPayload,
2725
+ payloadJson,
2726
+ byteCount: decoded.length,
2727
+ mode: "payload_json_base64"
2728
+ };
2729
+ }
2730
+ function resolveChunkWirePayload(parsed, syncSessionId, rawPayloadJson) {
2731
+ if (parsed.payloadJsonBase64) {
2732
+ return parseBase64ChunkPayload(parsed.payloadJsonBase64, {
2733
+ syncSessionId,
2734
+ chunkId: parsed.chunkId,
2735
+ family: parsed.family
2736
+ });
2737
+ }
2738
+ const payloadJson = rawPayloadJson ?? chunkPayloadJson(parsed.payload);
2739
+ return {
2740
+ payload: parsed.payload,
2741
+ payloadJson,
2742
+ byteCount: Buffer.byteLength(payloadJson, "utf8"),
2743
+ mode: "legacy_payload_object"
2744
+ };
2745
+ }
2688
2746
  function summarizeChunkPayload(family, payload) {
2689
2747
  switch (family) {
2690
2748
  case "sleep_nights":
@@ -2777,6 +2835,8 @@ export function startMobileHealthSyncSession(payload) {
2777
2835
  schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
2778
2836
  chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
2779
2837
  chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
2838
+ chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
2839
+ acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
2780
2840
  supportsCompression: false,
2781
2841
  acceptedFamilies: safeJsonParse(existing.requested_families_json, parsed.requestedFamilies),
2782
2842
  receivedChunkIds
@@ -2804,6 +2864,8 @@ export function startMobileHealthSyncSession(payload) {
2804
2864
  schemaVersion: HEALTH_MOBILE_SYNC_SCHEMA_VERSION,
2805
2865
  chunkTargetBytes: HEALTH_MOBILE_SYNC_CHUNK_TARGET_BYTES,
2806
2866
  chunkMaxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
2867
+ chunkPayloadEncoding: HEALTH_MOBILE_SYNC_CHUNK_PAYLOAD_ENCODING,
2868
+ acceptedPayloadEncodings: HEALTH_MOBILE_SYNC_ACCEPTED_CHUNK_PAYLOAD_ENCODINGS,
2807
2869
  supportsCompression: false,
2808
2870
  acceptedFamilies: parsed.requestedFamilies,
2809
2871
  receivedChunkIds: []
@@ -2834,18 +2896,37 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
2834
2896
  progress
2835
2897
  };
2836
2898
  }
2837
- const payloadJson = chunkPayloadJson(parsed.payload);
2838
- const checksumPayloadJson = rawPayloadJson ?? payloadJson;
2839
- const actualByteCount = Buffer.byteLength(checksumPayloadJson, "utf8");
2899
+ const wirePayload = resolveChunkWirePayload(parsed, syncSessionId, rawPayloadJson);
2900
+ const payloadJson = wirePayload.payloadJson;
2901
+ const actualByteCount = wirePayload.byteCount;
2840
2902
  if (actualByteCount > HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES) {
2841
2903
  throw new HttpError(413, "chunk_too_large", "The HealthKit sync chunk is too large.", {
2842
2904
  maxBytes: HEALTH_MOBILE_SYNC_CHUNK_MAX_BYTES,
2843
2905
  actualBytes: actualByteCount
2844
2906
  });
2845
2907
  }
2846
- const serverChecksum = chunkPayloadChecksum(checksumPayloadJson);
2908
+ const serverChecksum = chunkPayloadChecksum(payloadJson);
2847
2909
  if (parsed.checksumSha256 !== serverChecksum) {
2848
- throw new HttpError(409, "chunk_checksum_mismatch", "The HealthKit sync chunk checksum does not match its payload.", { actualBytes: actualByteCount });
2910
+ console.warn("[healthkit-sync] chunk checksum mismatch", {
2911
+ syncSessionId,
2912
+ chunkId: parsed.chunkId,
2913
+ family: parsed.family,
2914
+ mode: wirePayload.mode,
2915
+ clientChecksum: parsed.checksumSha256.slice(0, 12),
2916
+ serverChecksum: serverChecksum.slice(0, 12),
2917
+ clientByteCount: parsed.byteCount,
2918
+ actualByteCount
2919
+ });
2920
+ throw new HttpError(409, "chunk_checksum_mismatch", "The HealthKit sync chunk checksum does not match its payload.", {
2921
+ syncSessionId,
2922
+ chunkId: parsed.chunkId,
2923
+ family: parsed.family,
2924
+ actualBytes: actualByteCount,
2925
+ clientBytes: parsed.byteCount,
2926
+ clientChecksumPrefix: parsed.checksumSha256.slice(0, 12),
2927
+ serverChecksumPrefix: serverChecksum.slice(0, 12),
2928
+ mode: wirePayload.mode
2929
+ });
2849
2930
  }
2850
2931
  const now = nowIso();
2851
2932
  getDatabase()
@@ -2856,10 +2937,11 @@ export function ingestMobileHealthSyncChunk(syncSessionId, payload, rawPayloadJs
2856
2937
  )
2857
2938
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
2858
2939
  .run(mobileSyncChunkRecordId(), syncSessionId, parsed.chunkId, parsed.sequence, parsed.family, serverChecksum, parsed.recordCount, parsed.byteCount || actualByteCount, payloadJson, JSON.stringify({
2859
- ...summarizeChunkPayload(parsed.family, parsed.payload),
2940
+ ...summarizeChunkPayload(parsed.family, wirePayload.payload),
2860
2941
  clientByteCount: parsed.byteCount,
2861
2942
  actualByteCount,
2862
- serverChecksum
2943
+ serverChecksum,
2944
+ mode: wirePayload.mode
2863
2945
  }), now, now, now, now);
2864
2946
  const progress = updateMobileSyncSessionProgress(syncSessionId);
2865
2947
  return {
@@ -38,9 +38,17 @@ function sanitizeDiagnosticValue(value, depth = 0) {
38
38
  return value.toISOString();
39
39
  }
40
40
  if (value instanceof Error) {
41
+ const richError = value;
41
42
  return {
42
43
  name: value.name,
43
44
  message: value.message,
45
+ ...(typeof richError.code === "string" ? { code: richError.code } : {}),
46
+ ...(typeof richError.statusCode === "number"
47
+ ? { statusCode: richError.statusCode }
48
+ : {}),
49
+ ...(richError.details && typeof richError.details === "object"
50
+ ? { details: sanitizeDiagnosticValue(richError.details, depth + 1) }
51
+ : {}),
44
52
  stack: typeof value.stack === "string"
45
53
  ? sanitizeDiagnosticValue(value.stack, depth + 1)
46
54
  : null
@@ -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.73",
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.73",
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",