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