@datafog/fogclaw 0.1.4 → 0.1.6
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 +44 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +100 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127 -30
- package/dist/index.js.map +1 -1
- package/dist/scanner.d.ts +13 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +76 -2
- package/dist/scanner.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/plans/active/2026-02-17-feat-release-fogclaw-via-datafog-package-plan.md +24 -21
- package/docs/plugins/fogclaw.md +2 -0
- package/fogclaw.config.example.json +19 -1
- package/openclaw.plugin.json +103 -4
- package/package.json +1 -1
- package/src/config.ts +139 -2
- package/src/index.ts +185 -36
- package/src/scanner.ts +114 -8
- package/src/types.ts +19 -0
- package/tests/config.test.ts +55 -81
- package/tests/plugin-smoke.test.ts +30 -1
- package/tests/scanner.test.ts +61 -1
|
@@ -9,12 +9,14 @@ function createApi() {
|
|
|
9
9
|
return {
|
|
10
10
|
pluginConfig: {
|
|
11
11
|
model: "invalid:/not/real/model",
|
|
12
|
+
auditEnabled: true,
|
|
12
13
|
},
|
|
13
14
|
hooks,
|
|
14
15
|
tools,
|
|
15
16
|
logger: {
|
|
16
17
|
info: vi.fn(),
|
|
17
18
|
warn: vi.fn(),
|
|
19
|
+
error: vi.fn(),
|
|
18
20
|
},
|
|
19
21
|
on: vi.fn((event: string, handler: (event: any) => Promise<any>) => {
|
|
20
22
|
hooks.push({ event, handler });
|
|
@@ -46,14 +48,18 @@ describe("FogClaw OpenClaw plugin contract (integration path)", () => {
|
|
|
46
48
|
|
|
47
49
|
expect(typeof plugin.register).toBe("function");
|
|
48
50
|
expect(api.on).toHaveBeenCalledWith("before_agent_start", expect.any(Function));
|
|
49
|
-
expect(api.registerTool).toHaveBeenCalledTimes(
|
|
51
|
+
expect(api.registerTool).toHaveBeenCalledTimes(3);
|
|
50
52
|
|
|
51
53
|
const scanTool = api.tools.find((tool: any) => tool.id === "fogclaw_scan");
|
|
54
|
+
const previewTool = api.tools.find((tool: any) => tool.id === "fogclaw_preview");
|
|
52
55
|
const redactTool = api.tools.find((tool: any) => tool.id === "fogclaw_redact");
|
|
53
56
|
|
|
54
57
|
expect(scanTool).toBeDefined();
|
|
58
|
+
expect(previewTool).toBeDefined();
|
|
55
59
|
expect(redactTool).toBeDefined();
|
|
60
|
+
|
|
56
61
|
expect(scanTool.schema.required).toContain("text");
|
|
62
|
+
expect(previewTool.schema.required).toContain("text");
|
|
57
63
|
expect(redactTool.schema.required).toContain("text");
|
|
58
64
|
});
|
|
59
65
|
|
|
@@ -96,6 +102,29 @@ describe("FogClaw OpenClaw plugin contract (integration path)", () => {
|
|
|
96
102
|
expect(redactParsed.redacted_text).not.toContain("john@example.com");
|
|
97
103
|
});
|
|
98
104
|
|
|
105
|
+
it("supports preview output with action plan and redacted text", async () => {
|
|
106
|
+
const api = createApi();
|
|
107
|
+
|
|
108
|
+
plugin.register(api);
|
|
109
|
+
|
|
110
|
+
const previewTool = api.tools.find((tool: any) => tool.id === "fogclaw_preview");
|
|
111
|
+
|
|
112
|
+
const previewOutput = await previewTool.handler({
|
|
113
|
+
text: "Email me at john@example.com about Acme Corp tomorrow.",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const parsed = JSON.parse(previewOutput.content[0].text);
|
|
117
|
+
expect(parsed.totalEntities).toBeGreaterThan(0);
|
|
118
|
+
expect(parsed.actionPlan).toEqual(
|
|
119
|
+
expect.objectContaining({
|
|
120
|
+
blocked: expect.objectContaining({ count: expect.any(Number) }),
|
|
121
|
+
warned: expect.objectContaining({ count: expect.any(Number) }),
|
|
122
|
+
redacted: expect.objectContaining({ count: expect.any(Number) }),
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
expect(typeof parsed.redactedText).toBe("string");
|
|
126
|
+
});
|
|
127
|
+
|
|
99
128
|
it("passes custom_labels through tool path in real execution", async () => {
|
|
100
129
|
const api = createApi();
|
|
101
130
|
|
package/tests/scanner.test.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
beforeAll,
|
|
3
|
+
beforeEach,
|
|
4
|
+
afterAll,
|
|
5
|
+
describe,
|
|
6
|
+
it,
|
|
7
|
+
expect,
|
|
8
|
+
vi,
|
|
9
|
+
} from "vitest";
|
|
2
10
|
import fs from "node:fs/promises";
|
|
3
11
|
import os from "node:os";
|
|
4
12
|
import path from "node:path";
|
|
@@ -186,6 +194,58 @@ describe("Scanner", () => {
|
|
|
186
194
|
expect(email!.source).toBe("regex");
|
|
187
195
|
});
|
|
188
196
|
|
|
197
|
+
it("applies per-entity confidence threshold overrides", async () => {
|
|
198
|
+
const strictScanner = new Scanner(
|
|
199
|
+
makeConfig({
|
|
200
|
+
entityConfidenceThresholds: {
|
|
201
|
+
PERSON: 0.98,
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
await strictScanner.initialize();
|
|
206
|
+
|
|
207
|
+
const result = await strictScanner.scan("My name is John Smith.");
|
|
208
|
+
expect(result.entities.find((e) => e.label === "PERSON")).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("supports allowlist exact matches across global and per-entity rules", async () => {
|
|
212
|
+
const allowlistScanner = new Scanner(
|
|
213
|
+
makeConfig({
|
|
214
|
+
allowlist: {
|
|
215
|
+
values: ["john@example.com"],
|
|
216
|
+
patterns: ["^internal-"],
|
|
217
|
+
entities: {
|
|
218
|
+
PERSON: ["john smith"],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
await allowlistScanner.initialize();
|
|
224
|
+
|
|
225
|
+
const result = await allowlistScanner.scan(
|
|
226
|
+
"John Smith can be reached at john@example.com.",
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
expect(result.entities.find((e) => e.label === "EMAIL")).toBeUndefined();
|
|
230
|
+
expect(result.entities.find((e) => e.label === "PERSON")).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("applies allowlist regex patterns", async () => {
|
|
234
|
+
const allowlistScanner = new Scanner(
|
|
235
|
+
makeConfig({
|
|
236
|
+
allowlist: {
|
|
237
|
+
values: [],
|
|
238
|
+
patterns: ["test@example\\.com"],
|
|
239
|
+
entities: {},
|
|
240
|
+
},
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
await allowlistScanner.initialize();
|
|
244
|
+
|
|
245
|
+
const result = await allowlistScanner.scan("This is test@example.com for redaction.");
|
|
246
|
+
expect(result.entities.find((e) => e.label === "EMAIL")).toBeUndefined();
|
|
247
|
+
});
|
|
248
|
+
|
|
189
249
|
it("deduplicates overlapping spans keeping higher confidence", async () => {
|
|
190
250
|
// Scan text that might produce overlapping entities
|
|
191
251
|
// The dedup logic should keep higher confidence when spans overlap
|