@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.cjs
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
var crypto = require('@peac/crypto');
|
|
4
4
|
var captureCore = require('@peac/capture-core');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var fs2 = require('fs/promises');
|
|
7
|
+
var captureNode = require('@peac/capture-node');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
28
|
+
var captureCore__namespace = /*#__PURE__*/_interopNamespace(captureCore);
|
|
29
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
30
|
+
var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
|
|
5
31
|
|
|
6
32
|
// src/types.ts
|
|
7
33
|
var OPENCLAW_EXTENSION_KEYS = {
|
|
@@ -485,10 +511,10 @@ function createJwkSigner(jwk, issuer, audience) {
|
|
|
485
511
|
if (!jwk.d) {
|
|
486
512
|
throw new Error("JWK must include private key (d parameter)");
|
|
487
513
|
}
|
|
488
|
-
const privateKeyBytes =
|
|
514
|
+
const privateKeyBytes = crypto__namespace.base64urlDecode(jwk.d);
|
|
489
515
|
return {
|
|
490
516
|
async sign(payload) {
|
|
491
|
-
return
|
|
517
|
+
return crypto__namespace.sign(payload, privateKeyBytes, kid);
|
|
492
518
|
},
|
|
493
519
|
getKeyId() {
|
|
494
520
|
return kid;
|
|
@@ -518,8 +544,8 @@ async function resolveSigner(keyRef, issuer, audience) {
|
|
|
518
544
|
return createJwkSigner(jwk, issuer, audience);
|
|
519
545
|
}
|
|
520
546
|
case "file": {
|
|
521
|
-
const
|
|
522
|
-
const keyData = await
|
|
547
|
+
const fs3 = await import('fs');
|
|
548
|
+
const keyData = await fs3.promises.readFile(value, "utf-8");
|
|
523
549
|
const jwk = JSON.parse(keyData);
|
|
524
550
|
return createJwkSigner(jwk, issuer, audience);
|
|
525
551
|
}
|
|
@@ -542,32 +568,36 @@ function generateKeyId(jwk) {
|
|
|
542
568
|
return `k_${Math.abs(hash).toString(16).padStart(8, "0")}`;
|
|
543
569
|
}
|
|
544
570
|
async function createFileReceiptWriter(outputDir) {
|
|
545
|
-
const
|
|
571
|
+
const fs3 = await import('fs');
|
|
546
572
|
const pathModule = await import('path');
|
|
547
573
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
548
|
-
await
|
|
574
|
+
await fs3.promises.mkdir(resolvedOutputDir, { recursive: true });
|
|
549
575
|
return {
|
|
550
576
|
async write(receipt) {
|
|
551
577
|
const filename = `${receipt.rid}.peac.json`;
|
|
552
578
|
const filepath = pathModule.join(resolvedOutputDir, filename);
|
|
553
579
|
const tempPath = `${filepath}.tmp`;
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
580
|
+
let content;
|
|
581
|
+
const jwsParts = receipt.jws.split(".");
|
|
582
|
+
if (jwsParts.length !== 3) {
|
|
583
|
+
content = JSON.stringify({ _jws: receipt.jws }, null, 2);
|
|
584
|
+
} else {
|
|
585
|
+
try {
|
|
586
|
+
const payloadJson = Buffer.from(jwsParts[1], "base64url").toString("utf-8");
|
|
587
|
+
const payload = JSON.parse(payloadJson);
|
|
588
|
+
const { evidence, ...auth } = payload;
|
|
589
|
+
content = JSON.stringify({ auth, evidence, _jws: receipt.jws }, null, 2);
|
|
590
|
+
} catch {
|
|
591
|
+
content = JSON.stringify({ _jws: receipt.jws }, null, 2);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
await fs3.promises.writeFile(tempPath, content, "utf-8");
|
|
595
|
+
const fd = await fs3.promises.open(tempPath, "r");
|
|
566
596
|
await fd.sync();
|
|
567
597
|
await fd.close();
|
|
568
|
-
await
|
|
598
|
+
await fs3.promises.rename(tempPath, filepath);
|
|
569
599
|
try {
|
|
570
|
-
const dirFd = await
|
|
600
|
+
const dirFd = await fs3.promises.open(resolvedOutputDir, "r");
|
|
571
601
|
await dirFd.sync();
|
|
572
602
|
await dirFd.close();
|
|
573
603
|
} catch {
|
|
@@ -589,8 +619,8 @@ async function createPluginInstance(options) {
|
|
|
589
619
|
batchSize = 100,
|
|
590
620
|
onError
|
|
591
621
|
} = options;
|
|
592
|
-
const hasher = await
|
|
593
|
-
const session =
|
|
622
|
+
const hasher = await captureCore__namespace.createHasher();
|
|
623
|
+
const session = captureCore__namespace.createCaptureSession({
|
|
594
624
|
store,
|
|
595
625
|
dedupe,
|
|
596
626
|
hasher
|
|
@@ -691,6 +721,20 @@ var MAX_STATUS_FILE_SCAN = 1e3;
|
|
|
691
721
|
var MAX_QUERY_FILE_PARSE = 1e4;
|
|
692
722
|
var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
693
723
|
var ALLOWED_ALGORITHMS = ["EdDSA", "ES256", "ES384", "ES512", "RS256", "RS384", "RS512"];
|
|
724
|
+
function decodeReceiptPayload(receipt) {
|
|
725
|
+
const jws = receipt._jws;
|
|
726
|
+
if (typeof jws !== "string") return null;
|
|
727
|
+
const parts = jws.split(".");
|
|
728
|
+
if (parts.length !== 3) return null;
|
|
729
|
+
try {
|
|
730
|
+
const payloadJson = Buffer.from(parts[1], "base64url").toString("utf-8");
|
|
731
|
+
const payload = JSON.parse(payloadJson);
|
|
732
|
+
const { evidence, ...auth } = payload;
|
|
733
|
+
return { auth: auth ?? {}, evidence: evidence ?? {} };
|
|
734
|
+
} catch {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
694
738
|
var ALG_KEY_TYPE_MAP = {
|
|
695
739
|
EdDSA: { kty: "OKP", crv: ["Ed25519", "Ed448"] },
|
|
696
740
|
ES256: { kty: "EC", crv: ["P-256"] },
|
|
@@ -700,13 +744,14 @@ var ALG_KEY_TYPE_MAP = {
|
|
|
700
744
|
RS384: { kty: "RSA" },
|
|
701
745
|
RS512: { kty: "RSA" }
|
|
702
746
|
};
|
|
703
|
-
function createStatusTool(
|
|
747
|
+
function createStatusTool(getStats, outputDir) {
|
|
704
748
|
return {
|
|
705
749
|
name: "peac_receipts.status",
|
|
706
750
|
description: "Show PEAC receipts status: spool size, last receipt time, config summary",
|
|
707
751
|
parameters: {},
|
|
708
752
|
async execute() {
|
|
709
|
-
const
|
|
753
|
+
const stats = typeof getStats === "function" ? getStats() : getStats;
|
|
754
|
+
const fs3 = await import('fs');
|
|
710
755
|
const pathModule = await import('path');
|
|
711
756
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
712
757
|
let receiptCount = 0;
|
|
@@ -714,7 +759,7 @@ function createStatusTool(stats, outputDir) {
|
|
|
714
759
|
let oldestReceiptTime = null;
|
|
715
760
|
let countApproximate = false;
|
|
716
761
|
try {
|
|
717
|
-
const files = await
|
|
762
|
+
const files = await fs3.promises.readdir(resolvedOutputDir);
|
|
718
763
|
const receiptFiles = files.filter((f) => f.endsWith(".peac.json"));
|
|
719
764
|
receiptCount = receiptFiles.length;
|
|
720
765
|
const filesToScan = receiptFiles.slice(0, MAX_STATUS_FILE_SCAN);
|
|
@@ -722,7 +767,7 @@ function createStatusTool(stats, outputDir) {
|
|
|
722
767
|
if (filesToScan.length > 0) {
|
|
723
768
|
const fileStats = await Promise.all(
|
|
724
769
|
filesToScan.map(async (f) => {
|
|
725
|
-
const stat = await
|
|
770
|
+
const stat = await fs3.promises.stat(pathModule.join(resolvedOutputDir, f));
|
|
726
771
|
return { file: f, mtime: stat.mtime };
|
|
727
772
|
})
|
|
728
773
|
);
|
|
@@ -782,11 +827,11 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
782
827
|
}
|
|
783
828
|
},
|
|
784
829
|
async execute(params) {
|
|
785
|
-
const
|
|
830
|
+
const fs3 = await import('fs');
|
|
786
831
|
const pathModule = await import('path');
|
|
787
832
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
788
833
|
try {
|
|
789
|
-
const files = await
|
|
834
|
+
const files = await fs3.promises.readdir(resolvedOutputDir);
|
|
790
835
|
const receiptFiles = files.filter((f) => f.endsWith(".peac.json"));
|
|
791
836
|
if (receiptFiles.length === 0) {
|
|
792
837
|
return {
|
|
@@ -796,30 +841,48 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
796
841
|
};
|
|
797
842
|
}
|
|
798
843
|
const receipts = [];
|
|
844
|
+
const skippedReasons = { stat_error: 0, invalid_json: 0, malformed_jws: 0, filtered: 0 };
|
|
845
|
+
const skippedFiles = [];
|
|
799
846
|
for (const file of receiptFiles) {
|
|
800
847
|
const filePath = pathModule.join(resolvedOutputDir, file);
|
|
801
848
|
let stat;
|
|
802
849
|
try {
|
|
803
|
-
stat = await
|
|
850
|
+
stat = await fs3.promises.stat(filePath);
|
|
804
851
|
} catch {
|
|
852
|
+
skippedReasons.stat_error++;
|
|
853
|
+
skippedFiles.push(file);
|
|
805
854
|
continue;
|
|
806
855
|
}
|
|
807
856
|
if (params.since && stat.mtime < new Date(params.since)) {
|
|
857
|
+
skippedReasons.filtered++;
|
|
808
858
|
continue;
|
|
809
859
|
}
|
|
810
860
|
if (params.until && stat.mtime > new Date(params.until)) {
|
|
861
|
+
skippedReasons.filtered++;
|
|
811
862
|
continue;
|
|
812
863
|
}
|
|
813
864
|
let content;
|
|
814
865
|
try {
|
|
815
|
-
content = JSON.parse(await
|
|
866
|
+
content = JSON.parse(await fs3.promises.readFile(filePath, "utf-8"));
|
|
816
867
|
} catch {
|
|
817
868
|
logger.warn(`Skipping invalid JSON in ${file}`);
|
|
869
|
+
skippedReasons.invalid_json++;
|
|
870
|
+
skippedFiles.push(file);
|
|
818
871
|
continue;
|
|
819
872
|
}
|
|
820
873
|
if (params.workflow_id) {
|
|
821
|
-
const
|
|
874
|
+
const decoded = content._jws ? decodeReceiptPayload(content) : null;
|
|
875
|
+
if (content._jws && !decoded) {
|
|
876
|
+
logger.warn(`Skipping ${file}: _jws present but malformed`);
|
|
877
|
+
skippedReasons.malformed_jws++;
|
|
878
|
+
skippedFiles.push(file);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const authBlock = decoded?.auth ?? content?.auth;
|
|
882
|
+
const extensions = authBlock?.extensions;
|
|
883
|
+
const workflowExt = extensions?.["org.peacprotocol/workflow"];
|
|
822
884
|
if (workflowExt?.workflow_id !== params.workflow_id) {
|
|
885
|
+
skippedReasons.filtered++;
|
|
823
886
|
continue;
|
|
824
887
|
}
|
|
825
888
|
}
|
|
@@ -829,7 +892,12 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
829
892
|
return {
|
|
830
893
|
status: "ok",
|
|
831
894
|
message: "No receipts match the filter criteria",
|
|
832
|
-
receipt_count: 0
|
|
895
|
+
receipt_count: 0,
|
|
896
|
+
scanned_count: receiptFiles.length,
|
|
897
|
+
exported_count: 0,
|
|
898
|
+
skipped_count: skippedReasons.stat_error + skippedReasons.invalid_json + skippedReasons.malformed_jws + skippedReasons.filtered,
|
|
899
|
+
skipped_reasons: skippedReasons,
|
|
900
|
+
skipped_files: skippedFiles.length > 0 ? skippedFiles.slice(0, 100) : void 0
|
|
833
901
|
};
|
|
834
902
|
}
|
|
835
903
|
const manifest = {
|
|
@@ -849,15 +917,15 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
849
917
|
};
|
|
850
918
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
851
919
|
const bundleDir = params.output_path ? pathModule.resolve(params.output_path) : pathModule.join(resolvedOutputDir, `peac-bundle-${timestamp}`);
|
|
852
|
-
await
|
|
853
|
-
await
|
|
920
|
+
await fs3.promises.mkdir(bundleDir, { recursive: true });
|
|
921
|
+
await fs3.promises.writeFile(
|
|
854
922
|
pathModule.join(bundleDir, "manifest.json"),
|
|
855
923
|
JSON.stringify(manifest, null, 2)
|
|
856
924
|
);
|
|
857
925
|
const receiptsDir = pathModule.join(bundleDir, "receipts");
|
|
858
|
-
await
|
|
926
|
+
await fs3.promises.mkdir(receiptsDir, { recursive: true });
|
|
859
927
|
for (const receipt of receipts) {
|
|
860
|
-
await
|
|
928
|
+
await fs3.promises.writeFile(
|
|
861
929
|
pathModule.join(receiptsDir, receipt.file),
|
|
862
930
|
JSON.stringify(receipt.content, null, 2)
|
|
863
931
|
);
|
|
@@ -867,7 +935,12 @@ function createExportBundleTool(outputDir, logger) {
|
|
|
867
935
|
status: "ok",
|
|
868
936
|
message: `Exported ${receipts.length} receipts to directory`,
|
|
869
937
|
receipt_count: receipts.length,
|
|
870
|
-
bundle_path: bundleDir
|
|
938
|
+
bundle_path: bundleDir,
|
|
939
|
+
scanned_count: receiptFiles.length,
|
|
940
|
+
exported_count: receipts.length,
|
|
941
|
+
skipped_count: skippedReasons.stat_error + skippedReasons.invalid_json + skippedReasons.malformed_jws + skippedReasons.filtered,
|
|
942
|
+
skipped_reasons: skippedReasons,
|
|
943
|
+
skipped_files: skippedFiles.length > 0 ? skippedFiles.slice(0, 100) : void 0
|
|
871
944
|
};
|
|
872
945
|
} catch (error) {
|
|
873
946
|
logger.error("Export bundle failed:", error);
|
|
@@ -900,20 +973,20 @@ function createVerifyTool(logger) {
|
|
|
900
973
|
},
|
|
901
974
|
async execute(params) {
|
|
902
975
|
const verifyParams = params;
|
|
903
|
-
const
|
|
976
|
+
const fs3 = await import('fs');
|
|
904
977
|
const pathModule = await import('path');
|
|
905
978
|
try {
|
|
906
|
-
const stat = await
|
|
979
|
+
const stat = await fs3.promises.stat(verifyParams.path);
|
|
907
980
|
if (stat.isDirectory()) {
|
|
908
981
|
return await verifyBundle(
|
|
909
982
|
verifyParams.path,
|
|
910
983
|
verifyParams.jwks_path,
|
|
911
|
-
|
|
984
|
+
fs3,
|
|
912
985
|
pathModule,
|
|
913
986
|
logger
|
|
914
987
|
);
|
|
915
988
|
} else {
|
|
916
|
-
return await verifySingleReceipt(verifyParams.path, verifyParams.jwks_path,
|
|
989
|
+
return await verifySingleReceipt(verifyParams.path, verifyParams.jwks_path, fs3, logger);
|
|
917
990
|
}
|
|
918
991
|
} catch (error) {
|
|
919
992
|
logger.error("Verification failed:", error);
|
|
@@ -927,8 +1000,8 @@ function createVerifyTool(logger) {
|
|
|
927
1000
|
}
|
|
928
1001
|
};
|
|
929
1002
|
}
|
|
930
|
-
async function verifySingleReceipt(receiptPath, jwksPath,
|
|
931
|
-
const stat = await
|
|
1003
|
+
async function verifySingleReceipt(receiptPath, jwksPath, fs3, logger) {
|
|
1004
|
+
const stat = await fs3.promises.stat(receiptPath);
|
|
932
1005
|
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
933
1006
|
return {
|
|
934
1007
|
status: "error",
|
|
@@ -937,17 +1010,47 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
937
1010
|
errors: [`File size ${stat.size} bytes exceeds ${MAX_FILE_SIZE_BYTES} byte limit`]
|
|
938
1011
|
};
|
|
939
1012
|
}
|
|
940
|
-
const content = await
|
|
1013
|
+
const content = await fs3.promises.readFile(receiptPath, "utf-8");
|
|
941
1014
|
const receipt = JSON.parse(content);
|
|
942
1015
|
const errors = [];
|
|
943
1016
|
const warnings = [];
|
|
944
|
-
|
|
1017
|
+
let auth;
|
|
1018
|
+
let evidence;
|
|
1019
|
+
if (!receipt._jws) {
|
|
1020
|
+
auth = receipt.auth;
|
|
1021
|
+
evidence = receipt.evidence;
|
|
1022
|
+
warnings.push("Missing _jws field -- using unsigned top-level fields");
|
|
1023
|
+
} else {
|
|
1024
|
+
const decoded = decodeReceiptPayload(receipt);
|
|
1025
|
+
if (!decoded) {
|
|
1026
|
+
errors.push("_jws field present but payload could not be decoded (malformed JWS)");
|
|
1027
|
+
auth = void 0;
|
|
1028
|
+
evidence = void 0;
|
|
1029
|
+
} else {
|
|
1030
|
+
auth = decoded.auth;
|
|
1031
|
+
evidence = decoded.evidence;
|
|
1032
|
+
if (receipt.auth !== void 0 || receipt.evidence !== void 0) {
|
|
1033
|
+
const topAuth = receipt.auth;
|
|
1034
|
+
const topEvidence = receipt.evidence;
|
|
1035
|
+
const authMatch = topAuth === void 0 || JSON.stringify(topAuth) === JSON.stringify(decoded.auth);
|
|
1036
|
+
const evidenceMatch = topEvidence === void 0 || JSON.stringify(topEvidence) === JSON.stringify(decoded.evidence);
|
|
1037
|
+
if (!authMatch || !evidenceMatch) {
|
|
1038
|
+
errors.push(
|
|
1039
|
+
"Dual-representation mismatch: top-level auth/evidence differs from _jws payload"
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (!auth) {
|
|
945
1046
|
errors.push("Missing auth block");
|
|
946
1047
|
}
|
|
947
|
-
if (!
|
|
1048
|
+
if (!evidence) {
|
|
948
1049
|
errors.push("Missing evidence block");
|
|
949
1050
|
}
|
|
950
|
-
const
|
|
1051
|
+
const evidenceObj = evidence;
|
|
1052
|
+
const extensions = evidenceObj?.extensions;
|
|
1053
|
+
const interaction = extensions?.["org.peacprotocol/interaction@0.1"];
|
|
951
1054
|
if (interaction) {
|
|
952
1055
|
if (!interaction.interaction_id) {
|
|
953
1056
|
errors.push("Missing interaction_id");
|
|
@@ -955,7 +1058,8 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
955
1058
|
if (!interaction.kind) {
|
|
956
1059
|
errors.push("Missing kind");
|
|
957
1060
|
}
|
|
958
|
-
|
|
1061
|
+
const executor = interaction.executor;
|
|
1062
|
+
if (!executor?.platform) {
|
|
959
1063
|
errors.push("Missing executor.platform");
|
|
960
1064
|
}
|
|
961
1065
|
if (!interaction.started_at) {
|
|
@@ -966,7 +1070,8 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
966
1070
|
errors.push("completed_at is before started_at");
|
|
967
1071
|
}
|
|
968
1072
|
}
|
|
969
|
-
|
|
1073
|
+
const result = interaction.result;
|
|
1074
|
+
if (interaction.output && !result?.status) {
|
|
970
1075
|
errors.push("output present but result.status missing");
|
|
971
1076
|
}
|
|
972
1077
|
} else {
|
|
@@ -975,7 +1080,7 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
975
1080
|
if (jwksPath && receipt._jws) {
|
|
976
1081
|
try {
|
|
977
1082
|
const { verify, base64urlDecode: base64urlDecode2, base64urlDecodeString } = await import('@peac/crypto');
|
|
978
|
-
const jwks = JSON.parse(await
|
|
1083
|
+
const jwks = JSON.parse(await fs3.promises.readFile(jwksPath, "utf-8"));
|
|
979
1084
|
const jwsParts = receipt._jws.split(".");
|
|
980
1085
|
if (jwsParts.length !== 3) {
|
|
981
1086
|
errors.push("Invalid JWS format (expected 3 parts)");
|
|
@@ -1060,15 +1165,15 @@ async function verifySingleReceipt(receiptPath, jwksPath, fs, logger) {
|
|
|
1060
1165
|
message: errors.length === 0 ? "Receipt is valid" : "Receipt has validation errors",
|
|
1061
1166
|
errors: errors.length > 0 ? errors : void 0,
|
|
1062
1167
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
1063
|
-
receipt_id:
|
|
1168
|
+
receipt_id: auth?.rid,
|
|
1064
1169
|
interaction_id: interaction?.interaction_id
|
|
1065
1170
|
};
|
|
1066
1171
|
}
|
|
1067
|
-
async function verifyBundle(bundlePath, jwksPath,
|
|
1172
|
+
async function verifyBundle(bundlePath, jwksPath, fs3, pathModule, logger) {
|
|
1068
1173
|
const manifestPath = pathModule.join(bundlePath, "manifest.json");
|
|
1069
|
-
const manifest = JSON.parse(await
|
|
1174
|
+
const manifest = JSON.parse(await fs3.promises.readFile(manifestPath, "utf-8"));
|
|
1070
1175
|
const receiptsDir = pathModule.join(bundlePath, "receipts");
|
|
1071
|
-
const files = await
|
|
1176
|
+
const files = await fs3.promises.readdir(receiptsDir);
|
|
1072
1177
|
let validCount = 0;
|
|
1073
1178
|
let invalidCount = 0;
|
|
1074
1179
|
const allErrors = [];
|
|
@@ -1078,7 +1183,7 @@ async function verifyBundle(bundlePath, jwksPath, fs, pathModule, logger) {
|
|
|
1078
1183
|
const result = await verifySingleReceipt(
|
|
1079
1184
|
pathModule.join(receiptsDir, file),
|
|
1080
1185
|
jwksPath,
|
|
1081
|
-
|
|
1186
|
+
fs3,
|
|
1082
1187
|
logger
|
|
1083
1188
|
);
|
|
1084
1189
|
if (result.valid) {
|
|
@@ -1146,11 +1251,11 @@ function createQueryTool(outputDir, logger) {
|
|
|
1146
1251
|
}
|
|
1147
1252
|
},
|
|
1148
1253
|
async execute(params) {
|
|
1149
|
-
const
|
|
1254
|
+
const fs3 = await import('fs');
|
|
1150
1255
|
const pathModule = await import('path');
|
|
1151
1256
|
const resolvedOutputDir = pathModule.resolve(outputDir);
|
|
1152
1257
|
try {
|
|
1153
|
-
const files = await
|
|
1258
|
+
const files = await fs3.promises.readdir(resolvedOutputDir);
|
|
1154
1259
|
const receiptFiles = files.filter((f) => f.endsWith(".peac.json"));
|
|
1155
1260
|
const limit = Math.min(params.limit || 100, MAX_QUERY_FILE_PARSE);
|
|
1156
1261
|
const offset = params.offset || 0;
|
|
@@ -1158,7 +1263,7 @@ function createQueryTool(outputDir, logger) {
|
|
|
1158
1263
|
for (const file of receiptFiles.slice(0, MAX_QUERY_FILE_PARSE)) {
|
|
1159
1264
|
try {
|
|
1160
1265
|
const filePath = pathModule.join(resolvedOutputDir, file);
|
|
1161
|
-
const stat = await
|
|
1266
|
+
const stat = await fs3.promises.stat(filePath);
|
|
1162
1267
|
if (params.since && stat.mtime < new Date(params.since)) {
|
|
1163
1268
|
continue;
|
|
1164
1269
|
}
|
|
@@ -1175,10 +1280,11 @@ function createQueryTool(outputDir, logger) {
|
|
|
1175
1280
|
let skippedForFilters = 0;
|
|
1176
1281
|
let filesSkippedForSize = 0;
|
|
1177
1282
|
let filesSkippedForInvalidJson = 0;
|
|
1283
|
+
let filesSkippedForMalformedJws = 0;
|
|
1178
1284
|
for (const { file } of fileInfos) {
|
|
1179
1285
|
const filePath = pathModule.join(resolvedOutputDir, file);
|
|
1180
1286
|
try {
|
|
1181
|
-
const fileStat = await
|
|
1287
|
+
const fileStat = await fs3.promises.stat(filePath);
|
|
1182
1288
|
if (fileStat.size > MAX_FILE_SIZE_BYTES) {
|
|
1183
1289
|
filesSkippedForSize++;
|
|
1184
1290
|
continue;
|
|
@@ -1188,32 +1294,43 @@ function createQueryTool(outputDir, logger) {
|
|
|
1188
1294
|
}
|
|
1189
1295
|
let content;
|
|
1190
1296
|
try {
|
|
1191
|
-
content = JSON.parse(await
|
|
1297
|
+
content = JSON.parse(await fs3.promises.readFile(filePath, "utf-8"));
|
|
1192
1298
|
} catch {
|
|
1193
1299
|
filesSkippedForInvalidJson++;
|
|
1194
1300
|
continue;
|
|
1195
1301
|
}
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1302
|
+
const decoded = content._jws ? decodeReceiptPayload(content) : null;
|
|
1303
|
+
if (content._jws && !decoded) {
|
|
1304
|
+
filesSkippedForMalformedJws++;
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
const authBlock = decoded?.auth ?? content?.auth;
|
|
1308
|
+
const evidenceBlock = decoded?.evidence ?? content?.evidence;
|
|
1309
|
+
const evidenceExtensions = evidenceBlock?.extensions;
|
|
1310
|
+
const interaction = evidenceExtensions?.["org.peacprotocol/interaction@0.1"];
|
|
1311
|
+
const authExtensions = authBlock?.extensions;
|
|
1312
|
+
const workflow = authExtensions?.["org.peacprotocol/workflow"];
|
|
1198
1313
|
if (params.workflow_id && workflow?.workflow_id !== params.workflow_id) {
|
|
1199
1314
|
skippedForFilters++;
|
|
1200
1315
|
continue;
|
|
1201
1316
|
}
|
|
1202
|
-
|
|
1317
|
+
const tool = interaction?.tool;
|
|
1318
|
+
if (params.tool_name && tool?.name !== params.tool_name) {
|
|
1203
1319
|
skippedForFilters++;
|
|
1204
1320
|
continue;
|
|
1205
1321
|
}
|
|
1206
|
-
|
|
1322
|
+
const result = interaction?.result;
|
|
1323
|
+
if (params.status && result?.status !== params.status) {
|
|
1207
1324
|
skippedForFilters++;
|
|
1208
1325
|
continue;
|
|
1209
1326
|
}
|
|
1210
1327
|
matches.push({
|
|
1211
1328
|
file,
|
|
1212
|
-
receipt_id:
|
|
1329
|
+
receipt_id: authBlock?.rid,
|
|
1213
1330
|
interaction_id: interaction?.interaction_id,
|
|
1214
1331
|
workflow_id: workflow?.workflow_id,
|
|
1215
|
-
tool_name:
|
|
1216
|
-
status:
|
|
1332
|
+
tool_name: tool?.name,
|
|
1333
|
+
status: result?.status,
|
|
1217
1334
|
started_at: interaction?.started_at,
|
|
1218
1335
|
completed_at: interaction?.completed_at
|
|
1219
1336
|
});
|
|
@@ -1240,17 +1357,25 @@ function createQueryTool(outputDir, logger) {
|
|
|
1240
1357
|
if (filesSkippedForInvalidJson > 0) {
|
|
1241
1358
|
warnings.push(`${filesSkippedForInvalidJson} files skipped (invalid JSON)`);
|
|
1242
1359
|
}
|
|
1243
|
-
|
|
1360
|
+
if (filesSkippedForMalformedJws > 0) {
|
|
1361
|
+
warnings.push(`${filesSkippedForMalformedJws} files skipped (malformed JWS)`);
|
|
1362
|
+
}
|
|
1363
|
+
const totalSkipped = filesSkippedForSize + filesSkippedForInvalidJson + filesSkippedForMalformedJws;
|
|
1364
|
+
const hasTruncation = receiptFiles.length > MAX_QUERY_FILE_PARSE || totalSkipped > 0;
|
|
1244
1365
|
return {
|
|
1245
1366
|
status: "ok",
|
|
1246
1367
|
total: matches.length,
|
|
1247
1368
|
offset,
|
|
1248
1369
|
limit,
|
|
1249
1370
|
results: paginated,
|
|
1371
|
+
scanned_count: fileInfos.length,
|
|
1372
|
+
matched_count: matches.length,
|
|
1250
1373
|
truncated: hasTruncation,
|
|
1251
|
-
skipped: hasTruncation ? {
|
|
1374
|
+
skipped: hasTruncation || skippedForFilters > 0 ? {
|
|
1252
1375
|
too_large: filesSkippedForSize,
|
|
1253
1376
|
invalid_json: filesSkippedForInvalidJson,
|
|
1377
|
+
malformed_jws: filesSkippedForMalformedJws,
|
|
1378
|
+
filtered: skippedForFilters,
|
|
1254
1379
|
capped: receiptFiles.length > MAX_QUERY_FILE_PARSE ? receiptFiles.length - MAX_QUERY_FILE_PARSE : 0
|
|
1255
1380
|
} : void 0,
|
|
1256
1381
|
...warnings.length > 0 && { warning: warnings.join("; ") }
|
|
@@ -1269,10 +1394,161 @@ function createQueryTool(outputDir, logger) {
|
|
|
1269
1394
|
}
|
|
1270
1395
|
};
|
|
1271
1396
|
}
|
|
1397
|
+
var DEFAULT_DATA_DIR_NAME = ".openclaw/peac";
|
|
1398
|
+
var SPOOL_FILENAME = "spool.jsonl";
|
|
1399
|
+
var DEDUPE_FILENAME = "dedupe.idx";
|
|
1400
|
+
function defaultDataDir() {
|
|
1401
|
+
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
1402
|
+
return path__namespace.join(home, DEFAULT_DATA_DIR_NAME);
|
|
1403
|
+
}
|
|
1404
|
+
function noopLogger() {
|
|
1405
|
+
const noop = () => {
|
|
1406
|
+
};
|
|
1407
|
+
return { debug: noop, info: noop, warn: noop, error: noop };
|
|
1408
|
+
}
|
|
1409
|
+
async function activate(options) {
|
|
1410
|
+
const { config, spoolOptions } = options;
|
|
1411
|
+
const logger = options.logger ?? noopLogger();
|
|
1412
|
+
const dataDir = options.dataDir ?? defaultDataDir();
|
|
1413
|
+
await fs2__namespace.mkdir(dataDir, { recursive: true });
|
|
1414
|
+
try {
|
|
1415
|
+
await fs2__namespace.access(dataDir, (await import('constants')).W_OK);
|
|
1416
|
+
} catch {
|
|
1417
|
+
throw new Error(`Data directory is not writable: ${dataDir}`);
|
|
1418
|
+
}
|
|
1419
|
+
logger.info(`Resolving signing key from: ${config.signing.key_ref.split(":")[0]}:***`);
|
|
1420
|
+
const signer = await resolveSigner(
|
|
1421
|
+
config.signing.key_ref,
|
|
1422
|
+
config.signing.issuer,
|
|
1423
|
+
config.signing.audience
|
|
1424
|
+
);
|
|
1425
|
+
logger.info(`Signing key resolved. kid: ${signer.getKeyId()}`);
|
|
1426
|
+
const spoolPath = path__namespace.join(dataDir, SPOOL_FILENAME);
|
|
1427
|
+
const dedupePath = path__namespace.join(dataDir, DEDUPE_FILENAME);
|
|
1428
|
+
const store = await captureNode.createFsSpoolStore({
|
|
1429
|
+
filePath: spoolPath,
|
|
1430
|
+
maxEntries: spoolOptions?.maxEntries,
|
|
1431
|
+
maxFileBytes: spoolOptions?.maxFileBytes,
|
|
1432
|
+
autoCommitIntervalMs: spoolOptions?.autoCommitIntervalMs,
|
|
1433
|
+
lockOptions: spoolOptions?.lockOptions,
|
|
1434
|
+
onWarning: (msg) => logger.warn(`spool: ${msg}`)
|
|
1435
|
+
});
|
|
1436
|
+
const dedupe = await captureNode.createFsDedupeIndex({
|
|
1437
|
+
filePath: dedupePath
|
|
1438
|
+
});
|
|
1439
|
+
const outputDir = config.output_dir ?? path__namespace.join(dataDir, "receipts");
|
|
1440
|
+
const writer = await createFileReceiptWriter(outputDir);
|
|
1441
|
+
const instance = await createPluginInstance({
|
|
1442
|
+
signer,
|
|
1443
|
+
writer,
|
|
1444
|
+
adapterConfig: config,
|
|
1445
|
+
store,
|
|
1446
|
+
dedupe,
|
|
1447
|
+
drainIntervalMs: config.background?.drain_interval_ms,
|
|
1448
|
+
batchSize: config.background?.batch_size,
|
|
1449
|
+
onError: (err) => logger.error(`plugin error: ${err.message}`)
|
|
1450
|
+
});
|
|
1451
|
+
const tools = [
|
|
1452
|
+
createStatusTool(() => instance.getStats(), outputDir),
|
|
1453
|
+
createExportBundleTool(outputDir, logger),
|
|
1454
|
+
createVerifyTool(logger),
|
|
1455
|
+
createQueryTool(outputDir, logger)
|
|
1456
|
+
];
|
|
1457
|
+
const diag = captureNode.getFsSpoolDiagnostics(store);
|
|
1458
|
+
if (diag) {
|
|
1459
|
+
logger.info(
|
|
1460
|
+
`PEAC evidence export active. Spool: ${diag.entryCount} entries, ${diag.fileBytes} bytes. Receipts: ${outputDir}`
|
|
1461
|
+
);
|
|
1462
|
+
} else {
|
|
1463
|
+
logger.info(`PEAC evidence export active. Receipts: ${outputDir}`);
|
|
1464
|
+
}
|
|
1465
|
+
async function shutdown() {
|
|
1466
|
+
logger.info("Shutting down PEAC evidence export...");
|
|
1467
|
+
await instance.stop();
|
|
1468
|
+
await store.close();
|
|
1469
|
+
await dedupe.close();
|
|
1470
|
+
logger.info("PEAC evidence export stopped.");
|
|
1471
|
+
}
|
|
1472
|
+
return {
|
|
1473
|
+
instance,
|
|
1474
|
+
tools,
|
|
1475
|
+
hookHandler: instance.hookHandler,
|
|
1476
|
+
dataDir,
|
|
1477
|
+
flush: () => instance.backgroundService.drain(),
|
|
1478
|
+
shutdown
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
var DEFAULT_KEY_FILENAME = "signing-key.jwk";
|
|
1482
|
+
async function generateSigningKey(options) {
|
|
1483
|
+
const outputDir = options?.outputDir ?? ".";
|
|
1484
|
+
const filename = options?.filename ?? DEFAULT_KEY_FILENAME;
|
|
1485
|
+
const { privateKey: privateKeyBytes, publicKey: publicKeyBytes } = await crypto.generateKeypair();
|
|
1486
|
+
const d = crypto.base64urlEncode(privateKeyBytes);
|
|
1487
|
+
const x = crypto.base64urlEncode(publicKeyBytes);
|
|
1488
|
+
const privateJwk = {
|
|
1489
|
+
kty: "OKP",
|
|
1490
|
+
crv: "Ed25519",
|
|
1491
|
+
x,
|
|
1492
|
+
d,
|
|
1493
|
+
alg: "EdDSA",
|
|
1494
|
+
use: "sig"
|
|
1495
|
+
};
|
|
1496
|
+
const kid = generateKeyId(privateJwk);
|
|
1497
|
+
privateJwk.kid = kid;
|
|
1498
|
+
const publicJwk = {
|
|
1499
|
+
kty: "OKP",
|
|
1500
|
+
crv: "Ed25519",
|
|
1501
|
+
x,
|
|
1502
|
+
kid,
|
|
1503
|
+
alg: "EdDSA",
|
|
1504
|
+
use: "sig"
|
|
1505
|
+
};
|
|
1506
|
+
await fs2__namespace.mkdir(outputDir, { recursive: true });
|
|
1507
|
+
const keyPath = path__namespace.join(outputDir, filename);
|
|
1508
|
+
await fs2__namespace.writeFile(keyPath, JSON.stringify(privateJwk, null, 2) + "\n", "utf-8");
|
|
1509
|
+
try {
|
|
1510
|
+
await fs2__namespace.chmod(keyPath, 384);
|
|
1511
|
+
} catch {
|
|
1512
|
+
process.stderr.write(
|
|
1513
|
+
`Warning: Could not set file permissions on ${keyPath}. Ensure the file is not readable by other users.
|
|
1514
|
+
`
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
return { kid, keyPath, publicJwk, privateJwk };
|
|
1518
|
+
}
|
|
1519
|
+
async function keygenCli(args) {
|
|
1520
|
+
let outputDir;
|
|
1521
|
+
for (let i = 0; i < args.length; i++) {
|
|
1522
|
+
if (args[i] === "--output-dir" && i + 1 < args.length) {
|
|
1523
|
+
outputDir = args[i + 1];
|
|
1524
|
+
i++;
|
|
1525
|
+
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
1526
|
+
process.stdout.write(
|
|
1527
|
+
"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"
|
|
1528
|
+
);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const result = await generateSigningKey({ outputDir });
|
|
1533
|
+
process.stdout.write(`Key generated successfully.
|
|
1534
|
+
`);
|
|
1535
|
+
process.stdout.write(` kid: ${result.kid}
|
|
1536
|
+
`);
|
|
1537
|
+
process.stdout.write(` key file: ${result.keyPath}
|
|
1538
|
+
`);
|
|
1539
|
+
process.stdout.write(` public x: ${result.publicJwk.x}
|
|
1540
|
+
`);
|
|
1541
|
+
process.stdout.write(`
|
|
1542
|
+
To use with the OpenClaw adapter:
|
|
1543
|
+
`);
|
|
1544
|
+
process.stdout.write(` signing.key_ref: "file:${path__namespace.resolve(result.keyPath)}"
|
|
1545
|
+
`);
|
|
1546
|
+
}
|
|
1272
1547
|
|
|
1273
1548
|
exports.OPENCLAW_ERROR_CODES = OPENCLAW_ERROR_CODES;
|
|
1274
1549
|
exports.OPENCLAW_EXTENSION_KEYS = OPENCLAW_EXTENSION_KEYS;
|
|
1275
1550
|
exports.OPENCLAW_WARNING_CODES = OPENCLAW_WARNING_CODES;
|
|
1551
|
+
exports.activate = activate;
|
|
1276
1552
|
exports.buildWorkflowContext = buildWorkflowContext;
|
|
1277
1553
|
exports.captureBatch = captureBatch;
|
|
1278
1554
|
exports.captureParallel = captureParallel;
|
|
@@ -1289,6 +1565,8 @@ exports.createStatusTool = createStatusTool;
|
|
|
1289
1565
|
exports.createVerifyTool = createVerifyTool;
|
|
1290
1566
|
exports.extractWorkflowId = extractWorkflowId;
|
|
1291
1567
|
exports.generateKeyId = generateKeyId;
|
|
1568
|
+
exports.generateSigningKey = generateSigningKey;
|
|
1569
|
+
exports.keygenCli = keygenCli;
|
|
1292
1570
|
exports.mapToolCallEvent = mapToolCallEvent;
|
|
1293
1571
|
exports.mapToolCallEventBatch = mapToolCallEventBatch;
|
|
1294
1572
|
exports.resolveSigner = resolveSigner;
|