@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/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 = crypto.base64urlDecode(jwk.d);
514
+ const privateKeyBytes = crypto__namespace.base64urlDecode(jwk.d);
489
515
  return {
490
516
  async sign(payload) {
491
- return crypto.sign(payload, privateKeyBytes, kid);
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 fs = await import('fs');
522
- const keyData = await fs.promises.readFile(value, "utf-8");
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 fs = await import('fs');
571
+ const fs3 = await import('fs');
546
572
  const pathModule = await import('path');
547
573
  const resolvedOutputDir = pathModule.resolve(outputDir);
548
- await fs.promises.mkdir(resolvedOutputDir, { recursive: true });
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
- const content = JSON.stringify(
555
- {
556
- rid: receipt.rid,
557
- interaction_id: receipt.interaction_id,
558
- entry_digest: receipt.entry_digest,
559
- _jws: receipt.jws
560
- },
561
- null,
562
- 2
563
- );
564
- await fs.promises.writeFile(tempPath, content, "utf-8");
565
- const fd = await fs.promises.open(tempPath, "r");
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 fs.promises.rename(tempPath, filepath);
598
+ await fs3.promises.rename(tempPath, filepath);
569
599
  try {
570
- const dirFd = await fs.promises.open(resolvedOutputDir, "r");
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 captureCore.createHasher();
593
- const session = captureCore.createCaptureSession({
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(stats, outputDir) {
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 fs = await import('fs');
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 fs.promises.readdir(resolvedOutputDir);
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 fs.promises.stat(pathModule.join(resolvedOutputDir, f));
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 fs = await import('fs');
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 fs.promises.readdir(resolvedOutputDir);
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 fs.promises.stat(filePath);
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 fs.promises.readFile(filePath, "utf-8"));
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 workflowExt = content?.auth?.extensions?.["org.peacprotocol/workflow"];
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 fs.promises.mkdir(bundleDir, { recursive: true });
853
- await fs.promises.writeFile(
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 fs.promises.mkdir(receiptsDir, { recursive: true });
926
+ await fs3.promises.mkdir(receiptsDir, { recursive: true });
859
927
  for (const receipt of receipts) {
860
- await fs.promises.writeFile(
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 fs = await import('fs');
976
+ const fs3 = await import('fs');
904
977
  const pathModule = await import('path');
905
978
  try {
906
- const stat = await fs.promises.stat(verifyParams.path);
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
- fs,
984
+ fs3,
912
985
  pathModule,
913
986
  logger
914
987
  );
915
988
  } else {
916
- return await verifySingleReceipt(verifyParams.path, verifyParams.jwks_path, fs, logger);
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, fs, logger) {
931
- const stat = await fs.promises.stat(receiptPath);
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 fs.promises.readFile(receiptPath, "utf-8");
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
- if (!receipt.auth) {
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 (!receipt.evidence) {
1048
+ if (!evidence) {
948
1049
  errors.push("Missing evidence block");
949
1050
  }
950
- const interaction = receipt.evidence?.extensions?.["org.peacprotocol/interaction@0.1"];
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
- if (!interaction.executor?.platform) {
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
- if (interaction.output && !interaction.result?.status) {
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 fs.promises.readFile(jwksPath, "utf-8"));
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: receipt.auth?.rid,
1168
+ receipt_id: auth?.rid,
1064
1169
  interaction_id: interaction?.interaction_id
1065
1170
  };
1066
1171
  }
1067
- async function verifyBundle(bundlePath, jwksPath, fs, pathModule, logger) {
1172
+ async function verifyBundle(bundlePath, jwksPath, fs3, pathModule, logger) {
1068
1173
  const manifestPath = pathModule.join(bundlePath, "manifest.json");
1069
- const manifest = JSON.parse(await fs.promises.readFile(manifestPath, "utf-8"));
1174
+ const manifest = JSON.parse(await fs3.promises.readFile(manifestPath, "utf-8"));
1070
1175
  const receiptsDir = pathModule.join(bundlePath, "receipts");
1071
- const files = await fs.promises.readdir(receiptsDir);
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
- fs,
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 fs = await import('fs');
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 fs.promises.readdir(resolvedOutputDir);
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 fs.promises.stat(filePath);
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 fs.promises.stat(filePath);
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 fs.promises.readFile(filePath, "utf-8"));
1297
+ content = JSON.parse(await fs3.promises.readFile(filePath, "utf-8"));
1192
1298
  } catch {
1193
1299
  filesSkippedForInvalidJson++;
1194
1300
  continue;
1195
1301
  }
1196
- const interaction = content?.evidence?.extensions?.["org.peacprotocol/interaction@0.1"];
1197
- const workflow = content?.auth?.extensions?.["org.peacprotocol/workflow"];
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
- if (params.tool_name && interaction?.tool?.name !== params.tool_name) {
1317
+ const tool = interaction?.tool;
1318
+ if (params.tool_name && tool?.name !== params.tool_name) {
1203
1319
  skippedForFilters++;
1204
1320
  continue;
1205
1321
  }
1206
- if (params.status && interaction?.result?.status !== params.status) {
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: content.auth?.rid,
1329
+ receipt_id: authBlock?.rid,
1213
1330
  interaction_id: interaction?.interaction_id,
1214
1331
  workflow_id: workflow?.workflow_id,
1215
- tool_name: interaction?.tool?.name,
1216
- status: interaction?.result?.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
- const hasTruncation = receiptFiles.length > MAX_QUERY_FILE_PARSE || filesSkippedForSize > 0 || filesSkippedForInvalidJson > 0;
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;