@peac/adapter-openclaw 0.10.11 → 0.10.13
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/README.md +120 -118
- package/dist/activate.d.ts +87 -0
- package/dist/activate.d.ts.map +1 -0
- package/dist/index.cjs +347 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +326 -72
- package/dist/index.mjs.map +1 -1
- package/dist/keygen-cli.cjs +116 -0
- package/dist/keygen-cli.cjs.map +1 -0
- package/dist/keygen-cli.d.ts +7 -0
- package/dist/keygen-cli.d.ts.map +1 -0
- package/dist/keygen-cli.mjs +93 -0
- package/dist/keygen-cli.mjs.map +1 -0
- package/dist/keygen.d.ts +53 -0
- package/dist/keygen.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/tools.d.ts +5 -1
- package/dist/tools.d.ts.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +11 -7
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as crypto from '@peac/crypto';
|
|
2
|
+
import { canonicalize, generateKeypair, base64urlEncode as base64urlEncode$1 } from '@peac/crypto';
|
|
3
|
+
import * as captureCore from '@peac/capture-core';
|
|
4
|
+
import { toInteractionEvidence } from '@peac/capture-core';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as fs2 from 'fs/promises';
|
|
7
|
+
import { createFsSpoolStore, createFsDedupeIndex, getFsSpoolDiagnostics } from '@peac/capture-node';
|
|
3
8
|
|
|
4
9
|
// src/types.ts
|
|
5
10
|
var OPENCLAW_EXTENSION_KEYS = {
|
|
@@ -483,10 +488,10 @@ function createJwkSigner(jwk, issuer, audience) {
|
|
|
483
488
|
if (!jwk.d) {
|
|
484
489
|
throw new Error("JWK must include private key (d parameter)");
|
|
485
490
|
}
|
|
486
|
-
const privateKeyBytes = base64urlDecode(jwk.d);
|
|
491
|
+
const privateKeyBytes = crypto.base64urlDecode(jwk.d);
|
|
487
492
|
return {
|
|
488
493
|
async sign(payload) {
|
|
489
|
-
return sign(payload, privateKeyBytes, kid);
|
|
494
|
+
return crypto.sign(payload, privateKeyBytes, kid);
|
|
490
495
|
},
|
|
491
496
|
getKeyId() {
|
|
492
497
|
return kid;
|
|
@@ -516,8 +521,8 @@ async function resolveSigner(keyRef, issuer, audience) {
|
|
|
516
521
|
return createJwkSigner(jwk, issuer, audience);
|
|
517
522
|
}
|
|
518
523
|
case "file": {
|
|
519
|
-
const
|
|
520
|
-
const keyData = await
|
|
524
|
+
const fs3 = await import('fs');
|
|
525
|
+
const keyData = await fs3.promises.readFile(value, "utf-8");
|
|
521
526
|
const jwk = JSON.parse(keyData);
|
|
522
527
|
return createJwkSigner(jwk, issuer, audience);
|
|
523
528
|
}
|
|
@@ -540,32 +545,36 @@ function generateKeyId(jwk) {
|
|
|
540
545
|
return `k_${Math.abs(hash).toString(16).padStart(8, "0")}`;
|
|
541
546
|
}
|
|
542
547
|
async function createFileReceiptWriter(outputDir) {
|
|
543
|
-
const
|
|
548
|
+
const fs3 = await import('fs');
|
|
544
549
|
const pathModule = await import('path');
|
|
545
550
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
546
|
-
await
|
|
551
|
+
await fs3.promises.mkdir(resolvedOutputDir, { recursive: true });
|
|
547
552
|
return {
|
|
548
553
|
async write(receipt) {
|
|
549
554
|
const filename = `${receipt.rid}.peac.json`;
|
|
550
555
|
const filepath = pathModule.join(resolvedOutputDir, filename);
|
|
551
556
|
const tempPath = `${filepath}.tmp`;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
557
|
+
let content;
|
|
558
|
+
const jwsParts = receipt.jws.split(".");
|
|
559
|
+
if (jwsParts.length !== 3) {
|
|
560
|
+
content = JSON.stringify({ _jws: receipt.jws }, null, 2);
|
|
561
|
+
} else {
|
|
562
|
+
try {
|
|
563
|
+
const payloadJson = Buffer.from(jwsParts[1], "base64url").toString("utf-8");
|
|
564
|
+
const payload = JSON.parse(payloadJson);
|
|
565
|
+
const { evidence, ...auth } = payload;
|
|
566
|
+
content = JSON.stringify({ auth, evidence, _jws: receipt.jws }, null, 2);
|
|
567
|
+
} catch {
|
|
568
|
+
content = JSON.stringify({ _jws: receipt.jws }, null, 2);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
await fs3.promises.writeFile(tempPath, content, "utf-8");
|
|
572
|
+
const fd = await fs3.promises.open(tempPath, "r");
|
|
564
573
|
await fd.sync();
|
|
565
574
|
await fd.close();
|
|
566
|
-
await
|
|
575
|
+
await fs3.promises.rename(tempPath, filepath);
|
|
567
576
|
try {
|
|
568
|
-
const dirFd = await
|
|
577
|
+
const dirFd = await fs3.promises.open(resolvedOutputDir, "r");
|
|
569
578
|
await dirFd.sync();
|
|
570
579
|
await dirFd.close();
|
|
571
580
|
} catch {
|
|
@@ -587,8 +596,8 @@ async function createPluginInstance(options) {
|
|
|
587
596
|
batchSize = 100,
|
|
588
597
|
onError
|
|
589
598
|
} = options;
|
|
590
|
-
const hasher = await createHasher();
|
|
591
|
-
const session = createCaptureSession({
|
|
599
|
+
const hasher = await captureCore.createHasher();
|
|
600
|
+
const session = captureCore.createCaptureSession({
|
|
592
601
|
store,
|
|
593
602
|
dedupe,
|
|
594
603
|
hasher
|
|
@@ -689,6 +698,20 @@ var MAX_STATUS_FILE_SCAN = 1e3;
|
|
|
689
698
|
var MAX_QUERY_FILE_PARSE = 1e4;
|
|
690
699
|
var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
691
700
|
var ALLOWED_ALGORITHMS = ["EdDSA", "ES256", "ES384", "ES512", "RS256", "RS384", "RS512"];
|
|
701
|
+
function decodeReceiptPayload(receipt) {
|
|
702
|
+
const jws = receipt._jws;
|
|
703
|
+
if (typeof jws !== "string") return null;
|
|
704
|
+
const parts = jws.split(".");
|
|
705
|
+
if (parts.length !== 3) return null;
|
|
706
|
+
try {
|
|
707
|
+
const payloadJson = Buffer.from(parts[1], "base64url").toString("utf-8");
|
|
708
|
+
const payload = JSON.parse(payloadJson);
|
|
709
|
+
const { evidence, ...auth } = payload;
|
|
710
|
+
return { auth: auth ?? {}, evidence: evidence ?? {} };
|
|
711
|
+
} catch {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
692
715
|
var ALG_KEY_TYPE_MAP = {
|
|
693
716
|
EdDSA: { kty: "OKP", crv: ["Ed25519", "Ed448"] },
|
|
694
717
|
ES256: { kty: "EC", crv: ["P-256"] },
|
|
@@ -698,13 +721,14 @@ var ALG_KEY_TYPE_MAP = {
|
|
|
698
721
|
RS384: { kty: "RSA" },
|
|
699
722
|
RS512: { kty: "RSA" }
|
|
700
723
|
};
|
|
701
|
-
function createStatusTool(
|
|
724
|
+
function createStatusTool(getStats, outputDir) {
|
|
702
725
|
return {
|
|
703
726
|
name: "peac_receipts.status",
|
|
704
727
|
description: "Show PEAC receipts status: spool size, last receipt time, config summary",
|
|
705
728
|
parameters: {},
|
|
706
729
|
async execute() {
|
|
707
|
-
const
|
|
730
|
+
const stats = typeof getStats === "function" ? getStats() : getStats;
|
|
731
|
+
const fs3 = await import('fs');
|
|
708
732
|
const pathModule = await import('path');
|
|
709
733
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
710
734
|
let receiptCount = 0;
|
|
@@ -712,7 +736,7 @@ function createStatusTool(stats, outputDir) {
|
|
|
712
736
|
let oldestReceiptTime = null;
|
|
713
737
|
let countApproximate = false;
|
|
714
738
|
try {
|
|
715
|
-
const files = await
|
|
739
|
+
const files = await fs3.promises.readdir(resolvedOutputDir);
|
|
716
740
|
const receiptFiles = files.filter((f) => f.endsWith(".peac.json"));
|
|
717
741
|
receiptCount = receiptFiles.length;
|
|
718
742
|
const filesToScan = receiptFiles.slice(0, MAX_STATUS_FILE_SCAN);
|
|
@@ -720,7 +744,7 @@ function createStatusTool(stats, outputDir) {
|
|
|
720
744
|
if (filesToScan.length > 0) {
|
|
721
745
|
const fileStats = await Promise.all(
|
|
722
746
|
filesToScan.map(async (f) => {
|
|
723
|
-
const stat = await
|
|
747
|
+
const stat = await fs3.promises.stat(pathModule.join(resolvedOutputDir, f));
|
|
724
748
|
return { file: f, mtime: stat.mtime };
|
|
725
749
|
})
|
|
726
750
|
);
|
|
@@ -780,11 +804,11 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
780
804
|
}
|
|
781
805
|
},
|
|
782
806
|
async execute(params) {
|
|
783
|
-
const
|
|
807
|
+
const fs3 = await import('fs');
|
|
784
808
|
const pathModule = await import('path');
|
|
785
809
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
786
810
|
try {
|
|
787
|
-
const files = await
|
|
811
|
+
const files = await fs3.promises.readdir(resolvedOutputDir);
|
|
788
812
|
const receiptFiles = files.filter((f) => f.endsWith(".peac.json"));
|
|
789
813
|
if (receiptFiles.length === 0) {
|
|
790
814
|
return {
|
|
@@ -794,30 +818,48 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
794
818
|
};
|
|
795
819
|
}
|
|
796
820
|
const receipts = [];
|
|
821
|
+
const skippedReasons = { stat_error: 0, invalid_json: 0, malformed_jws: 0, filtered: 0 };
|
|
822
|
+
const skippedFiles = [];
|
|
797
823
|
for (const file of receiptFiles) {
|
|
798
824
|
const filePath = pathModule.join(resolvedOutputDir, file);
|
|
799
825
|
let stat;
|
|
800
826
|
try {
|
|
801
|
-
stat = await
|
|
827
|
+
stat = await fs3.promises.stat(filePath);
|
|
802
828
|
} catch {
|
|
829
|
+
skippedReasons.stat_error++;
|
|
830
|
+
skippedFiles.push(file);
|
|
803
831
|
continue;
|
|
804
832
|
}
|
|
805
833
|
if (params.since && stat.mtime < new Date(params.since)) {
|
|
834
|
+
skippedReasons.filtered++;
|
|
806
835
|
continue;
|
|
807
836
|
}
|
|
808
837
|
if (params.until && stat.mtime > new Date(params.until)) {
|
|
838
|
+
skippedReasons.filtered++;
|
|
809
839
|
continue;
|
|
810
840
|
}
|
|
811
841
|
let content;
|
|
812
842
|
try {
|
|
813
|
-
content = JSON.parse(await
|
|
843
|
+
content = JSON.parse(await fs3.promises.readFile(filePath, "utf-8"));
|
|
814
844
|
} catch {
|
|
815
845
|
logger.warn(`Skipping invalid JSON in ${file}`);
|
|
846
|
+
skippedReasons.invalid_json++;
|
|
847
|
+
skippedFiles.push(file);
|
|
816
848
|
continue;
|
|
817
849
|
}
|
|
818
850
|
if (params.workflow_id) {
|
|
819
|
-
const
|
|
851
|
+
const decoded = content._jws ? decodeReceiptPayload(content) : null;
|
|
852
|
+
if (content._jws && !decoded) {
|
|
853
|
+
logger.warn(`Skipping ${file}: _jws present but malformed`);
|
|
854
|
+
skippedReasons.malformed_jws++;
|
|
855
|
+
skippedFiles.push(file);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const authBlock = decoded?.auth ?? content?.auth;
|
|
859
|
+
const extensions = authBlock?.extensions;
|
|
860
|
+
const workflowExt = extensions?.["org.peacprotocol/workflow"];
|
|
820
861
|
if (workflowExt?.workflow_id !== params.workflow_id) {
|
|
862
|
+
skippedReasons.filtered++;
|
|
821
863
|
continue;
|
|
822
864
|
}
|
|
823
865
|
}
|
|
@@ -827,7 +869,12 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
827
869
|
return {
|
|
828
870
|
status: "ok",
|
|
829
871
|
message: "No receipts match the filter criteria",
|
|
830
|
-
receipt_count: 0
|
|
872
|
+
receipt_count: 0,
|
|
873
|
+
scanned_count: receiptFiles.length,
|
|
874
|
+
exported_count: 0,
|
|
875
|
+
skipped_count: skippedReasons.stat_error + skippedReasons.invalid_json + skippedReasons.malformed_jws + skippedReasons.filtered,
|
|
876
|
+
skipped_reasons: skippedReasons,
|
|
877
|
+
skipped_files: skippedFiles.length > 0 ? skippedFiles.slice(0, 100) : void 0
|
|
831
878
|
};
|
|
832
879
|
}
|
|
833
880
|
const manifest = {
|
|
@@ -847,15 +894,15 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
847
894
|
};
|
|
848
895
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
849
896
|
const bundleDir = params.output_path ? pathModule.resolve(params.output_path) : pathModule.join(resolvedOutputDir, `peac-bundle-${timestamp}`);
|
|
850
|
-
await
|
|
851
|
-
await
|
|
897
|
+
await fs3.promises.mkdir(bundleDir, { recursive: true });
|
|
898
|
+
await fs3.promises.writeFile(
|
|
852
899
|
pathModule.join(bundleDir, "manifest.json"),
|
|
853
900
|
JSON.stringify(manifest, null, 2)
|
|
854
901
|
);
|
|
855
902
|
const receiptsDir = pathModule.join(bundleDir, "receipts");
|
|
856
|
-
await
|
|
903
|
+
await fs3.promises.mkdir(receiptsDir, { recursive: true });
|
|
857
904
|
for (const receipt of receipts) {
|
|
858
|
-
await
|
|
905
|
+
await fs3.promises.writeFile(
|
|
859
906
|
pathModule.join(receiptsDir, receipt.file),
|
|
860
907
|
JSON.stringify(receipt.content, null, 2)
|
|
861
908
|
);
|
|
@@ -865,7 +912,12 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
865
912
|
status: "ok",
|
|
866
913
|
message: `Exported ${receipts.length} receipts to directory`,
|
|
867
914
|
receipt_count: receipts.length,
|
|
868
|
-
bundle_path: bundleDir
|
|
915
|
+
bundle_path: bundleDir,
|
|
916
|
+
scanned_count: receiptFiles.length,
|
|
917
|
+
exported_count: receipts.length,
|
|
918
|
+
skipped_count: skippedReasons.stat_error + skippedReasons.invalid_json + skippedReasons.malformed_jws + skippedReasons.filtered,
|
|
919
|
+
skipped_reasons: skippedReasons,
|
|
920
|
+
skipped_files: skippedFiles.length > 0 ? skippedFiles.slice(0, 100) : void 0
|
|
869
921
|
};
|
|
870
922
|
} catch (error) {
|
|
871
923
|
logger.error("Export bundle failed:", error);
|
|
@@ -898,20 +950,20 @@ function createVerifyTool(logger) {
|
|
|
898
950
|
},
|
|
899
951
|
async execute(params) {
|
|
900
952
|
const verifyParams = params;
|
|
901
|
-
const
|
|
953
|
+
const fs3 = await import('fs');
|
|
902
954
|
const pathModule = await import('path');
|
|
903
955
|
try {
|
|
904
|
-
const stat = await
|
|
956
|
+
const stat = await fs3.promises.stat(verifyParams.path);
|
|
905
957
|
if (stat.isDirectory()) {
|
|
906
958
|
return await verifyBundle(
|
|
907
959
|
verifyParams.path,
|
|
908
960
|
verifyParams.jwks_path,
|
|
909
|
-
|
|
961
|
+
fs3,
|
|
910
962
|
pathModule,
|
|
911
963
|
logger
|
|
912
964
|
);
|
|
913
965
|
} else {
|
|
914
|
-
return await verifySingleReceipt(verifyParams.path, verifyParams.jwks_path,
|
|
966
|
+
return await verifySingleReceipt(verifyParams.path, verifyParams.jwks_path, fs3, logger);
|
|
915
967
|
}
|
|
916
968
|
} catch (error) {
|
|
917
969
|
logger.error("Verification failed:", error);
|
|
@@ -925,8 +977,8 @@ function createVerifyTool(logger) {
|
|
|
925
977
|
}
|
|
926
978
|
};
|
|
927
979
|
}
|
|
928
|
-
async function verifySingleReceipt(receiptPath, jwksPath,
|
|
929
|
-
const stat = await
|
|
980
|
+
async function verifySingleReceipt(receiptPath, jwksPath, fs3, logger) {
|
|
981
|
+
const stat = await fs3.promises.stat(receiptPath);
|
|
930
982
|
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
931
983
|
return {
|
|
932
984
|
status: "error",
|
|
@@ -935,17 +987,47 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
935
987
|
errors: [`File size ${stat.size} bytes exceeds ${MAX_FILE_SIZE_BYTES} byte limit`]
|
|
936
988
|
};
|
|
937
989
|
}
|
|
938
|
-
const content = await
|
|
990
|
+
const content = await fs3.promises.readFile(receiptPath, "utf-8");
|
|
939
991
|
const receipt = JSON.parse(content);
|
|
940
992
|
const errors = [];
|
|
941
993
|
const warnings = [];
|
|
942
|
-
|
|
994
|
+
let auth;
|
|
995
|
+
let evidence;
|
|
996
|
+
if (!receipt._jws) {
|
|
997
|
+
auth = receipt.auth;
|
|
998
|
+
evidence = receipt.evidence;
|
|
999
|
+
warnings.push("Missing _jws field -- using unsigned top-level fields");
|
|
1000
|
+
} else {
|
|
1001
|
+
const decoded = decodeReceiptPayload(receipt);
|
|
1002
|
+
if (!decoded) {
|
|
1003
|
+
errors.push("_jws field present but payload could not be decoded (malformed JWS)");
|
|
1004
|
+
auth = void 0;
|
|
1005
|
+
evidence = void 0;
|
|
1006
|
+
} else {
|
|
1007
|
+
auth = decoded.auth;
|
|
1008
|
+
evidence = decoded.evidence;
|
|
1009
|
+
if (receipt.auth !== void 0 || receipt.evidence !== void 0) {
|
|
1010
|
+
const topAuth = receipt.auth;
|
|
1011
|
+
const topEvidence = receipt.evidence;
|
|
1012
|
+
const authMatch = topAuth === void 0 || JSON.stringify(topAuth) === JSON.stringify(decoded.auth);
|
|
1013
|
+
const evidenceMatch = topEvidence === void 0 || JSON.stringify(topEvidence) === JSON.stringify(decoded.evidence);
|
|
1014
|
+
if (!authMatch || !evidenceMatch) {
|
|
1015
|
+
errors.push(
|
|
1016
|
+
"Dual-representation mismatch: top-level auth/evidence differs from _jws payload"
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (!auth) {
|
|
943
1023
|
errors.push("Missing auth block");
|
|
944
1024
|
}
|
|
945
|
-
if (!
|
|
1025
|
+
if (!evidence) {
|
|
946
1026
|
errors.push("Missing evidence block");
|
|
947
1027
|
}
|
|
948
|
-
const
|
|
1028
|
+
const evidenceObj = evidence;
|
|
1029
|
+
const extensions = evidenceObj?.extensions;
|
|
1030
|
+
const interaction = extensions?.["org.peacprotocol/interaction@0.1"];
|
|
949
1031
|
if (interaction) {
|
|
950
1032
|
if (!interaction.interaction_id) {
|
|
951
1033
|
errors.push("Missing interaction_id");
|
|
@@ -953,7 +1035,8 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
953
1035
|
if (!interaction.kind) {
|
|
954
1036
|
errors.push("Missing kind");
|
|
955
1037
|
}
|
|
956
|
-
|
|
1038
|
+
const executor = interaction.executor;
|
|
1039
|
+
if (!executor?.platform) {
|
|
957
1040
|
errors.push("Missing executor.platform");
|
|
958
1041
|
}
|
|
959
1042
|
if (!interaction.started_at) {
|
|
@@ -964,7 +1047,8 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
964
1047
|
errors.push("completed_at is before started_at");
|
|
965
1048
|
}
|
|
966
1049
|
}
|
|
967
|
-
|
|
1050
|
+
const result = interaction.result;
|
|
1051
|
+
if (interaction.output && !result?.status) {
|
|
968
1052
|
errors.push("output present but result.status missing");
|
|
969
1053
|
}
|
|
970
1054
|
} else {
|
|
@@ -973,7 +1057,7 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
973
1057
|
if (jwksPath && receipt._jws) {
|
|
974
1058
|
try {
|
|
975
1059
|
const { verify, base64urlDecode: base64urlDecode2, base64urlDecodeString } = await import('@peac/crypto');
|
|
976
|
-
const jwks = JSON.parse(await
|
|
1060
|
+
const jwks = JSON.parse(await fs3.promises.readFile(jwksPath, "utf-8"));
|
|
977
1061
|
const jwsParts = receipt._jws.split(".");
|
|
978
1062
|
if (jwsParts.length !== 3) {
|
|
979
1063
|
errors.push("Invalid JWS format (expected 3 parts)");
|
|
@@ -1058,15 +1142,15 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
1058
1142
|
message: errors.length === 0 ? "Receipt is valid" : "Receipt has validation errors",
|
|
1059
1143
|
errors: errors.length > 0 ? errors : void 0,
|
|
1060
1144
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
1061
|
-
receipt_id:
|
|
1145
|
+
receipt_id: auth?.rid,
|
|
1062
1146
|
interaction_id: interaction?.interaction_id
|
|
1063
1147
|
};
|
|
1064
1148
|
}
|
|
1065
|
-
async function verifyBundle(bundlePath, jwksPath,
|
|
1149
|
+
async function verifyBundle(bundlePath, jwksPath, fs3, pathModule, logger) {
|
|
1066
1150
|
const manifestPath = pathModule.join(bundlePath, "manifest.json");
|
|
1067
|
-
const manifest = JSON.parse(await
|
|
1151
|
+
const manifest = JSON.parse(await fs3.promises.readFile(manifestPath, "utf-8"));
|
|
1068
1152
|
const receiptsDir = pathModule.join(bundlePath, "receipts");
|
|
1069
|
-
const files = await
|
|
1153
|
+
const files = await fs3.promises.readdir(receiptsDir);
|
|
1070
1154
|
let validCount = 0;
|
|
1071
1155
|
let invalidCount = 0;
|
|
1072
1156
|
const allErrors = [];
|
|
@@ -1076,7 +1160,7 @@ async function verifyBundle(bundlePath, jwksPath, fs, pathModule, logger) {
|
|
|
1076
1160
|
const result = await verifySingleReceipt(
|
|
1077
1161
|
pathModule.join(receiptsDir, file),
|
|
1078
1162
|
jwksPath,
|
|
1079
|
-
|
|
1163
|
+
fs3,
|
|
1080
1164
|
logger
|
|
1081
1165
|
);
|
|
1082
1166
|
if (result.valid) {
|
|
@@ -1144,11 +1228,11 @@ function createQueryTool(outputDir, logger) {
|
|
|
1144
1228
|
}
|
|
1145
1229
|
},
|
|
1146
1230
|
async execute(params) {
|
|
1147
|
-
const
|
|
1231
|
+
const fs3 = await import('fs');
|
|
1148
1232
|
const pathModule = await import('path');
|
|
1149
1233
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
1150
1234
|
try {
|
|
1151
|
-
const files = await
|
|
1235
|
+
const files = await fs3.promises.readdir(resolvedOutputDir);
|
|
1152
1236
|
const receiptFiles = files.filter((f) => f.endsWith(".peac.json"));
|
|
1153
1237
|
const limit = Math.min(params.limit || 100, MAX_QUERY_FILE_PARSE);
|
|
1154
1238
|
const offset = params.offset || 0;
|
|
@@ -1156,7 +1240,7 @@ function createQueryTool(outputDir, logger) {
|
|
|
1156
1240
|
for (const file of receiptFiles.slice(0, MAX_QUERY_FILE_PARSE)) {
|
|
1157
1241
|
try {
|
|
1158
1242
|
const filePath = pathModule.join(resolvedOutputDir, file);
|
|
1159
|
-
const stat = await
|
|
1243
|
+
const stat = await fs3.promises.stat(filePath);
|
|
1160
1244
|
if (params.since && stat.mtime < new Date(params.since)) {
|
|
1161
1245
|
continue;
|
|
1162
1246
|
}
|
|
@@ -1173,10 +1257,11 @@ function createQueryTool(outputDir, logger) {
|
|
|
1173
1257
|
let skippedForFilters = 0;
|
|
1174
1258
|
let filesSkippedForSize = 0;
|
|
1175
1259
|
let filesSkippedForInvalidJson = 0;
|
|
1260
|
+
let filesSkippedForMalformedJws = 0;
|
|
1176
1261
|
for (const { file } of fileInfos) {
|
|
1177
1262
|
const filePath = pathModule.join(resolvedOutputDir, file);
|
|
1178
1263
|
try {
|
|
1179
|
-
const fileStat = await
|
|
1264
|
+
const fileStat = await fs3.promises.stat(filePath);
|
|
1180
1265
|
if (fileStat.size > MAX_FILE_SIZE_BYTES) {
|
|
1181
1266
|
filesSkippedForSize++;
|
|
1182
1267
|
continue;
|
|
@@ -1186,32 +1271,43 @@ function createQueryTool(outputDir, logger) {
|
|
|
1186
1271
|
}
|
|
1187
1272
|
let content;
|
|
1188
1273
|
try {
|
|
1189
|
-
content = JSON.parse(await
|
|
1274
|
+
content = JSON.parse(await fs3.promises.readFile(filePath, "utf-8"));
|
|
1190
1275
|
} catch {
|
|
1191
1276
|
filesSkippedForInvalidJson++;
|
|
1192
1277
|
continue;
|
|
1193
1278
|
}
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1279
|
+
const decoded = content._jws ? decodeReceiptPayload(content) : null;
|
|
1280
|
+
if (content._jws && !decoded) {
|
|
1281
|
+
filesSkippedForMalformedJws++;
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
const authBlock = decoded?.auth ?? content?.auth;
|
|
1285
|
+
const evidenceBlock = decoded?.evidence ?? content?.evidence;
|
|
1286
|
+
const evidenceExtensions = evidenceBlock?.extensions;
|
|
1287
|
+
const interaction = evidenceExtensions?.["org.peacprotocol/interaction@0.1"];
|
|
1288
|
+
const authExtensions = authBlock?.extensions;
|
|
1289
|
+
const workflow = authExtensions?.["org.peacprotocol/workflow"];
|
|
1196
1290
|
if (params.workflow_id && workflow?.workflow_id !== params.workflow_id) {
|
|
1197
1291
|
skippedForFilters++;
|
|
1198
1292
|
continue;
|
|
1199
1293
|
}
|
|
1200
|
-
|
|
1294
|
+
const tool = interaction?.tool;
|
|
1295
|
+
if (params.tool_name && tool?.name !== params.tool_name) {
|
|
1201
1296
|
skippedForFilters++;
|
|
1202
1297
|
continue;
|
|
1203
1298
|
}
|
|
1204
|
-
|
|
1299
|
+
const result = interaction?.result;
|
|
1300
|
+
if (params.status && result?.status !== params.status) {
|
|
1205
1301
|
skippedForFilters++;
|
|
1206
1302
|
continue;
|
|
1207
1303
|
}
|
|
1208
1304
|
matches.push({
|
|
1209
1305
|
file,
|
|
1210
|
-
receipt_id:
|
|
1306
|
+
receipt_id: authBlock?.rid,
|
|
1211
1307
|
interaction_id: interaction?.interaction_id,
|
|
1212
1308
|
workflow_id: workflow?.workflow_id,
|
|
1213
|
-
tool_name:
|
|
1214
|
-
status:
|
|
1309
|
+
tool_name: tool?.name,
|
|
1310
|
+
status: result?.status,
|
|
1215
1311
|
started_at: interaction?.started_at,
|
|
1216
1312
|
completed_at: interaction?.completed_at
|
|
1217
1313
|
});
|
|
@@ -1238,17 +1334,25 @@ function createQueryTool(outputDir, logger) {
|
|
|
1238
1334
|
if (filesSkippedForInvalidJson > 0) {
|
|
1239
1335
|
warnings.push(`${filesSkippedForInvalidJson} files skipped (invalid JSON)`);
|
|
1240
1336
|
}
|
|
1241
|
-
|
|
1337
|
+
if (filesSkippedForMalformedJws > 0) {
|
|
1338
|
+
warnings.push(`${filesSkippedForMalformedJws} files skipped (malformed JWS)`);
|
|
1339
|
+
}
|
|
1340
|
+
const totalSkipped = filesSkippedForSize + filesSkippedForInvalidJson + filesSkippedForMalformedJws;
|
|
1341
|
+
const hasTruncation = receiptFiles.length > MAX_QUERY_FILE_PARSE || totalSkipped > 0;
|
|
1242
1342
|
return {
|
|
1243
1343
|
status: "ok",
|
|
1244
1344
|
total: matches.length,
|
|
1245
1345
|
offset,
|
|
1246
1346
|
limit,
|
|
1247
1347
|
results: paginated,
|
|
1348
|
+
scanned_count: fileInfos.length,
|
|
1349
|
+
matched_count: matches.length,
|
|
1248
1350
|
truncated: hasTruncation,
|
|
1249
|
-
skipped: hasTruncation ? {
|
|
1351
|
+
skipped: hasTruncation || skippedForFilters > 0 ? {
|
|
1250
1352
|
too_large: filesSkippedForSize,
|
|
1251
1353
|
invalid_json: filesSkippedForInvalidJson,
|
|
1354
|
+
malformed_jws: filesSkippedForMalformedJws,
|
|
1355
|
+
filtered: skippedForFilters,
|
|
1252
1356
|
capped: receiptFiles.length > MAX_QUERY_FILE_PARSE ? receiptFiles.length - MAX_QUERY_FILE_PARSE : 0
|
|
1253
1357
|
} : void 0,
|
|
1254
1358
|
...warnings.length > 0 && { warning: warnings.join("; ") }
|
|
@@ -1267,7 +1371,157 @@ function createQueryTool(outputDir, logger) {
|
|
|
1267
1371
|
}
|
|
1268
1372
|
};
|
|
1269
1373
|
}
|
|
1374
|
+
var DEFAULT_DATA_DIR_NAME = ".openclaw/peac";
|
|
1375
|
+
var SPOOL_FILENAME = "spool.jsonl";
|
|
1376
|
+
var DEDUPE_FILENAME = "dedupe.idx";
|
|
1377
|
+
function defaultDataDir() {
|
|
1378
|
+
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
1379
|
+
return path.join(home, DEFAULT_DATA_DIR_NAME);
|
|
1380
|
+
}
|
|
1381
|
+
function noopLogger() {
|
|
1382
|
+
const noop = () => {
|
|
1383
|
+
};
|
|
1384
|
+
return { debug: noop, info: noop, warn: noop, error: noop };
|
|
1385
|
+
}
|
|
1386
|
+
async function activate(options) {
|
|
1387
|
+
const { config, spoolOptions } = options;
|
|
1388
|
+
const logger = options.logger ?? noopLogger();
|
|
1389
|
+
const dataDir = options.dataDir ?? defaultDataDir();
|
|
1390
|
+
await fs2.mkdir(dataDir, { recursive: true });
|
|
1391
|
+
try {
|
|
1392
|
+
await fs2.access(dataDir, (await import('constants')).W_OK);
|
|
1393
|
+
} catch {
|
|
1394
|
+
throw new Error(`Data directory is not writable: ${dataDir}`);
|
|
1395
|
+
}
|
|
1396
|
+
logger.info(`Resolving signing key from: ${config.signing.key_ref.split(":")[0]}:***`);
|
|
1397
|
+
const signer = await resolveSigner(
|
|
1398
|
+
config.signing.key_ref,
|
|
1399
|
+
config.signing.issuer,
|
|
1400
|
+
config.signing.audience
|
|
1401
|
+
);
|
|
1402
|
+
logger.info(`Signing key resolved. kid: ${signer.getKeyId()}`);
|
|
1403
|
+
const spoolPath = path.join(dataDir, SPOOL_FILENAME);
|
|
1404
|
+
const dedupePath = path.join(dataDir, DEDUPE_FILENAME);
|
|
1405
|
+
const store = await createFsSpoolStore({
|
|
1406
|
+
filePath: spoolPath,
|
|
1407
|
+
maxEntries: spoolOptions?.maxEntries,
|
|
1408
|
+
maxFileBytes: spoolOptions?.maxFileBytes,
|
|
1409
|
+
autoCommitIntervalMs: spoolOptions?.autoCommitIntervalMs,
|
|
1410
|
+
lockOptions: spoolOptions?.lockOptions,
|
|
1411
|
+
onWarning: (msg) => logger.warn(`spool: ${msg}`)
|
|
1412
|
+
});
|
|
1413
|
+
const dedupe = await createFsDedupeIndex({
|
|
1414
|
+
filePath: dedupePath
|
|
1415
|
+
});
|
|
1416
|
+
const outputDir = config.output_dir ?? path.join(dataDir, "receipts");
|
|
1417
|
+
const writer = await createFileReceiptWriter(outputDir);
|
|
1418
|
+
const instance = await createPluginInstance({
|
|
1419
|
+
signer,
|
|
1420
|
+
writer,
|
|
1421
|
+
adapterConfig: config,
|
|
1422
|
+
store,
|
|
1423
|
+
dedupe,
|
|
1424
|
+
drainIntervalMs: config.background?.drain_interval_ms,
|
|
1425
|
+
batchSize: config.background?.batch_size,
|
|
1426
|
+
onError: (err) => logger.error(`plugin error: ${err.message}`)
|
|
1427
|
+
});
|
|
1428
|
+
const tools = [
|
|
1429
|
+
createStatusTool(() => instance.getStats(), outputDir),
|
|
1430
|
+
createExportBundleTool(outputDir, logger),
|
|
1431
|
+
createVerifyTool(logger),
|
|
1432
|
+
createQueryTool(outputDir, logger)
|
|
1433
|
+
];
|
|
1434
|
+
const diag = getFsSpoolDiagnostics(store);
|
|
1435
|
+
if (diag) {
|
|
1436
|
+
logger.info(
|
|
1437
|
+
`PEAC evidence export active. Spool: ${diag.entryCount} entries, ${diag.fileBytes} bytes. Receipts: ${outputDir}`
|
|
1438
|
+
);
|
|
1439
|
+
} else {
|
|
1440
|
+
logger.info(`PEAC evidence export active. Receipts: ${outputDir}`);
|
|
1441
|
+
}
|
|
1442
|
+
async function shutdown() {
|
|
1443
|
+
logger.info("Shutting down PEAC evidence export...");
|
|
1444
|
+
await instance.stop();
|
|
1445
|
+
await store.close();
|
|
1446
|
+
await dedupe.close();
|
|
1447
|
+
logger.info("PEAC evidence export stopped.");
|
|
1448
|
+
}
|
|
1449
|
+
return {
|
|
1450
|
+
instance,
|
|
1451
|
+
tools,
|
|
1452
|
+
hookHandler: instance.hookHandler,
|
|
1453
|
+
dataDir,
|
|
1454
|
+
flush: () => instance.backgroundService.drain(),
|
|
1455
|
+
shutdown
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
var DEFAULT_KEY_FILENAME = "signing-key.jwk";
|
|
1459
|
+
async function generateSigningKey(options) {
|
|
1460
|
+
const outputDir = options?.outputDir ?? ".";
|
|
1461
|
+
const filename = options?.filename ?? DEFAULT_KEY_FILENAME;
|
|
1462
|
+
const { privateKey: privateKeyBytes, publicKey: publicKeyBytes } = await generateKeypair();
|
|
1463
|
+
const d = base64urlEncode$1(privateKeyBytes);
|
|
1464
|
+
const x = base64urlEncode$1(publicKeyBytes);
|
|
1465
|
+
const privateJwk = {
|
|
1466
|
+
kty: "OKP",
|
|
1467
|
+
crv: "Ed25519",
|
|
1468
|
+
x,
|
|
1469
|
+
d,
|
|
1470
|
+
alg: "EdDSA",
|
|
1471
|
+
use: "sig"
|
|
1472
|
+
};
|
|
1473
|
+
const kid = generateKeyId(privateJwk);
|
|
1474
|
+
privateJwk.kid = kid;
|
|
1475
|
+
const publicJwk = {
|
|
1476
|
+
kty: "OKP",
|
|
1477
|
+
crv: "Ed25519",
|
|
1478
|
+
x,
|
|
1479
|
+
kid,
|
|
1480
|
+
alg: "EdDSA",
|
|
1481
|
+
use: "sig"
|
|
1482
|
+
};
|
|
1483
|
+
await fs2.mkdir(outputDir, { recursive: true });
|
|
1484
|
+
const keyPath = path.join(outputDir, filename);
|
|
1485
|
+
await fs2.writeFile(keyPath, JSON.stringify(privateJwk, null, 2) + "\n", "utf-8");
|
|
1486
|
+
try {
|
|
1487
|
+
await fs2.chmod(keyPath, 384);
|
|
1488
|
+
} catch {
|
|
1489
|
+
process.stderr.write(
|
|
1490
|
+
`Warning: Could not set file permissions on ${keyPath}. Ensure the file is not readable by other users.
|
|
1491
|
+
`
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
return { kid, keyPath, publicJwk, privateJwk };
|
|
1495
|
+
}
|
|
1496
|
+
async function keygenCli(args) {
|
|
1497
|
+
let outputDir;
|
|
1498
|
+
for (let i = 0; i < args.length; i++) {
|
|
1499
|
+
if (args[i] === "--output-dir" && i + 1 < args.length) {
|
|
1500
|
+
outputDir = args[i + 1];
|
|
1501
|
+
i++;
|
|
1502
|
+
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
1503
|
+
process.stdout.write(
|
|
1504
|
+
"Usage: peac-keygen [--output-dir <dir>]\n\nGenerate an Ed25519 signing key for PEAC receipt signing.\n\nOptions:\n --output-dir <dir> Directory for key file (default: current directory)\n --help, -h Show this help message\n"
|
|
1505
|
+
);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const result = await generateSigningKey({ outputDir });
|
|
1510
|
+
process.stdout.write(`Key generated successfully.
|
|
1511
|
+
`);
|
|
1512
|
+
process.stdout.write(` kid: ${result.kid}
|
|
1513
|
+
`);
|
|
1514
|
+
process.stdout.write(` key file: ${result.keyPath}
|
|
1515
|
+
`);
|
|
1516
|
+
process.stdout.write(` public x: ${result.publicJwk.x}
|
|
1517
|
+
`);
|
|
1518
|
+
process.stdout.write(`
|
|
1519
|
+
To use with the OpenClaw adapter:
|
|
1520
|
+
`);
|
|
1521
|
+
process.stdout.write(` signing.key_ref: "file:${path.resolve(result.keyPath)}"
|
|
1522
|
+
`);
|
|
1523
|
+
}
|
|
1270
1524
|
|
|
1271
|
-
export { OPENCLAW_ERROR_CODES, OPENCLAW_EXTENSION_KEYS, OPENCLAW_WARNING_CODES, buildWorkflowContext, captureBatch, captureParallel, createBackgroundService, createExportBundleTool, createFileReceiptWriter, createHookHandler, createJwkSigner, createPluginInstance, createQueryTool, createReceiptEmitter, createSessionHistoryTailer, createStatusTool, createVerifyTool, extractWorkflowId, generateKeyId, mapToolCallEvent, mapToolCallEventBatch, resolveSigner };
|
|
1525
|
+
export { OPENCLAW_ERROR_CODES, OPENCLAW_EXTENSION_KEYS, OPENCLAW_WARNING_CODES, activate, buildWorkflowContext, captureBatch, captureParallel, createBackgroundService, createExportBundleTool, createFileReceiptWriter, createHookHandler, createJwkSigner, createPluginInstance, createQueryTool, createReceiptEmitter, createSessionHistoryTailer, createStatusTool, createVerifyTool, extractWorkflowId, generateKeyId, generateSigningKey, keygenCli, mapToolCallEvent, mapToolCallEventBatch, resolveSigner };
|
|
1272
1526
|
//# sourceMappingURL=index.mjs.map
|
|
1273
1527
|
//# sourceMappingURL=index.mjs.map
|