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:
|
|
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
|
|
2838
|
-
const
|
|
2839
|
-
const actualByteCount =
|
|
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(
|
|
2908
|
+
const serverChecksum = chunkPayloadChecksum(payloadJson);
|
|
2847
2909
|
if (parsed.checksumSha256 !== serverChecksum) {
|
|
2848
|
-
|
|
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,
|
|
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
|
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.73",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCapabilities": [
|
package/package.json
CHANGED