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