@datafog/fogclaw 0.2.0 → 0.3.0
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/CHANGELOG.md +11 -0
- package/dist/backlog-tools.d.ts +57 -0
- package/dist/backlog-tools.d.ts.map +1 -0
- package/dist/backlog-tools.js +173 -0
- package/dist/backlog-tools.js.map +1 -0
- package/dist/backlog.d.ts +82 -0
- package/dist/backlog.d.ts.map +1 -0
- package/dist/backlog.js +169 -0
- package/dist/backlog.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +87 -2
- package/dist/index.js.map +1 -1
- package/dist/message-sending-handler.d.ts +2 -1
- package/dist/message-sending-handler.d.ts.map +1 -1
- package/dist/message-sending-handler.js +5 -1
- package/dist/message-sending-handler.js.map +1 -1
- package/dist/tool-result-handler.d.ts +2 -1
- package/dist/tool-result-handler.d.ts.map +1 -1
- package/dist/tool-result-handler.js +5 -1
- package/dist/tool-result-handler.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/openclaw.plugin.json +11 -1
- package/package.json +7 -1
- package/.github/workflows/harness-docs.yml +0 -30
- package/AGENTS.md +0 -28
- package/docs/DATA.md +0 -28
- package/docs/DESIGN.md +0 -17
- package/docs/DOMAIN_DOCS.md +0 -30
- package/docs/FRONTEND.md +0 -24
- package/docs/OBSERVABILITY.md +0 -32
- package/docs/PLANS.md +0 -171
- package/docs/PRODUCT_SENSE.md +0 -20
- package/docs/RELIABILITY.md +0 -60
- package/docs/SECURITY.md +0 -52
- package/docs/design-docs/core-beliefs.md +0 -17
- package/docs/design-docs/index.md +0 -8
- package/docs/generated/README.md +0 -36
- package/docs/generated/memory.md +0 -1
- package/docs/plans/2026-02-16-fogclaw-design.md +0 -172
- package/docs/plans/2026-02-16-fogclaw-implementation.md +0 -1606
- package/docs/plans/README.md +0 -15
- package/docs/plans/active/2026-02-16-feat-openclaw-official-submission-plan.md +0 -386
- package/docs/plans/active/2026-02-17-feat-release-fogclaw-via-datafog-package-plan.md +0 -328
- package/docs/plans/active/2026-02-17-feat-submit-fogclaw-to-openclaw-plan.md +0 -244
- package/docs/plans/active/2026-02-17-feat-tool-result-pii-scanning-plan.md +0 -293
- package/docs/plans/tech-debt-tracker.md +0 -42
- package/docs/plugins/fogclaw.md +0 -101
- package/docs/runbooks/address-review-findings.md +0 -30
- package/docs/runbooks/ci-failures.md +0 -46
- package/docs/runbooks/code-review.md +0 -34
- package/docs/runbooks/merge-change.md +0 -28
- package/docs/runbooks/pull-request.md +0 -45
- package/docs/runbooks/record-evidence.md +0 -43
- package/docs/runbooks/reproduce-bug.md +0 -42
- package/docs/runbooks/respond-to-feedback.md +0 -42
- package/docs/runbooks/review-findings.md +0 -31
- package/docs/runbooks/submit-openclaw-plugin.md +0 -68
- package/docs/runbooks/update-agents-md.md +0 -59
- package/docs/runbooks/update-domain-docs.md +0 -42
- package/docs/runbooks/validate-current-state.md +0 -41
- package/docs/runbooks/verify-release.md +0 -69
- package/docs/specs/2026-02-16-feat-openclaw-official-submission-spec.md +0 -115
- package/docs/specs/2026-02-17-feat-outbound-message-pii-scanning-spec.md +0 -93
- package/docs/specs/2026-02-17-feat-submit-fogclaw-to-openclaw.md +0 -125
- package/docs/specs/2026-02-17-feat-tool-result-pii-scanning-spec.md +0 -122
- package/docs/specs/README.md +0 -5
- package/docs/specs/index.md +0 -8
- package/docs/spikes/README.md +0 -8
- package/fogclaw.config.example.json +0 -33
- package/scripts/ci/he-docs-config.json +0 -123
- package/scripts/ci/he-docs-drift.sh +0 -112
- package/scripts/ci/he-docs-lint.sh +0 -234
- package/scripts/ci/he-plans-lint.sh +0 -354
- package/scripts/ci/he-runbooks-lint.sh +0 -445
- package/scripts/ci/he-specs-lint.sh +0 -258
- package/scripts/ci/he-spikes-lint.sh +0 -249
- package/scripts/runbooks/select-runbooks.sh +0 -154
- package/src/config.ts +0 -183
- package/src/engines/gliner.ts +0 -240
- package/src/engines/regex.ts +0 -71
- package/src/extract.ts +0 -98
- package/src/index.ts +0 -381
- package/src/message-sending-handler.ts +0 -87
- package/src/redactor.ts +0 -51
- package/src/scanner.ts +0 -196
- package/src/tool-result-handler.ts +0 -133
- package/src/types.ts +0 -75
- package/tests/config.test.ts +0 -78
- package/tests/extract.test.ts +0 -185
- package/tests/gliner.test.ts +0 -289
- package/tests/message-sending-handler.test.ts +0 -244
- package/tests/plugin-smoke.test.ts +0 -250
- package/tests/redactor.test.ts +0 -320
- package/tests/regex.test.ts +0 -345
- package/tests/scanner.test.ts +0 -348
- package/tests/tool-result-handler.test.ts +0 -329
- package/tsconfig.json +0 -20
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { createToolResultHandler } from "../src/tool-result-handler.js";
|
|
3
|
-
import { RegexEngine } from "../src/engines/regex.js";
|
|
4
|
-
import type { FogClawConfig } from "../src/types.js";
|
|
5
|
-
|
|
6
|
-
function makeConfig(overrides: Partial<FogClawConfig> = {}): FogClawConfig {
|
|
7
|
-
return {
|
|
8
|
-
enabled: true,
|
|
9
|
-
guardrail_mode: "redact",
|
|
10
|
-
redactStrategy: "token",
|
|
11
|
-
model: "test",
|
|
12
|
-
confidence_threshold: 0.5,
|
|
13
|
-
custom_entities: [],
|
|
14
|
-
entityActions: {},
|
|
15
|
-
entityConfidenceThresholds: {},
|
|
16
|
-
allowlist: { values: [], patterns: [], entities: {} },
|
|
17
|
-
auditEnabled: false,
|
|
18
|
-
...overrides,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function makeLogger() {
|
|
23
|
-
return {
|
|
24
|
-
info: vi.fn(),
|
|
25
|
-
warn: vi.fn(),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("createToolResultHandler", () => {
|
|
30
|
-
const regexEngine = new RegexEngine();
|
|
31
|
-
|
|
32
|
-
it("returns a synchronous function (not async)", () => {
|
|
33
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
34
|
-
expect(typeof handler).toBe("function");
|
|
35
|
-
|
|
36
|
-
// Verify it does not return a Promise
|
|
37
|
-
const result = handler(
|
|
38
|
-
{ message: "no pii here" },
|
|
39
|
-
{},
|
|
40
|
-
);
|
|
41
|
-
// undefined is expected for no-PII — not a Promise
|
|
42
|
-
expect(result).toBeUndefined();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("redacts SSN in a plain string message", () => {
|
|
46
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
47
|
-
const result = handler(
|
|
48
|
-
{ message: "SSN is 123-45-6789" },
|
|
49
|
-
{},
|
|
50
|
-
);
|
|
51
|
-
expect(result).toBeDefined();
|
|
52
|
-
expect(result!.message).toBe("SSN is [SSN_1]");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("redacts email in a content-string message", () => {
|
|
56
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
57
|
-
const result = handler(
|
|
58
|
-
{ message: { role: "toolResult", content: "Contact john@example.com" } },
|
|
59
|
-
{},
|
|
60
|
-
);
|
|
61
|
-
expect(result).toBeDefined();
|
|
62
|
-
const msg = result!.message as Record<string, unknown>;
|
|
63
|
-
expect(msg.content).toBe("Contact [EMAIL_1]");
|
|
64
|
-
expect(msg.role).toBe("toolResult");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("redacts phone number in content block array", () => {
|
|
68
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
69
|
-
const result = handler(
|
|
70
|
-
{
|
|
71
|
-
message: {
|
|
72
|
-
content: [{ type: "text", text: "Call 555-123-4567 please" }],
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
{},
|
|
76
|
-
);
|
|
77
|
-
expect(result).toBeDefined();
|
|
78
|
-
const msg = result!.message as Record<string, unknown>;
|
|
79
|
-
const content = msg.content as Array<Record<string, unknown>>;
|
|
80
|
-
expect(content[0].text).toBe("Call [PHONE_1] please");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("redacts multiple PII types in one message", () => {
|
|
84
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
85
|
-
const result = handler(
|
|
86
|
-
{ message: "Call 555-123-4567 or email john@example.com" },
|
|
87
|
-
{},
|
|
88
|
-
);
|
|
89
|
-
expect(result).toBeDefined();
|
|
90
|
-
const text = result!.message as string;
|
|
91
|
-
expect(text).toContain("[PHONE_1]");
|
|
92
|
-
expect(text).toContain("[EMAIL_1]");
|
|
93
|
-
expect(text).not.toContain("555-123-4567");
|
|
94
|
-
expect(text).not.toContain("john@example.com");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("returns void when no PII is found", () => {
|
|
98
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
99
|
-
const result = handler(
|
|
100
|
-
{ message: "This is clean text with no sensitive data." },
|
|
101
|
-
{},
|
|
102
|
-
);
|
|
103
|
-
expect(result).toBeUndefined();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("returns void for empty string message", () => {
|
|
107
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
108
|
-
expect(handler({ message: "" }, {})).toBeUndefined();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("returns void for null message", () => {
|
|
112
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
113
|
-
expect(handler({ message: null }, {})).toBeUndefined();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("returns void for message with no extractable text", () => {
|
|
117
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
118
|
-
expect(
|
|
119
|
-
handler(
|
|
120
|
-
{
|
|
121
|
-
message: {
|
|
122
|
-
content: [{ type: "image", source: { data: "base64" } }],
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
{},
|
|
126
|
-
),
|
|
127
|
-
).toBeUndefined();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("respects allowlist — global values", () => {
|
|
131
|
-
const config = makeConfig({
|
|
132
|
-
allowlist: {
|
|
133
|
-
values: ["noreply@example.com"],
|
|
134
|
-
patterns: [],
|
|
135
|
-
entities: {},
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
const handler = createToolResultHandler(config, regexEngine);
|
|
139
|
-
const result = handler(
|
|
140
|
-
{ message: "Contact noreply@example.com for help" },
|
|
141
|
-
{},
|
|
142
|
-
);
|
|
143
|
-
// The allowlisted email should not be redacted
|
|
144
|
-
expect(result).toBeUndefined();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("respects allowlist — global patterns", () => {
|
|
148
|
-
const config = makeConfig({
|
|
149
|
-
allowlist: {
|
|
150
|
-
values: [],
|
|
151
|
-
patterns: ["^internal-"],
|
|
152
|
-
entities: {},
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
const handler = createToolResultHandler(config, regexEngine);
|
|
156
|
-
const result = handler(
|
|
157
|
-
{ message: "Contact internal-noreply@company.com" },
|
|
158
|
-
{},
|
|
159
|
-
);
|
|
160
|
-
expect(result).toBeUndefined();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("respects allowlist — per-entity values", () => {
|
|
164
|
-
const config = makeConfig({
|
|
165
|
-
allowlist: {
|
|
166
|
-
values: [],
|
|
167
|
-
patterns: [],
|
|
168
|
-
entities: { EMAIL: ["public@example.com"] },
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
const handler = createToolResultHandler(config, regexEngine);
|
|
172
|
-
const result = handler(
|
|
173
|
-
{ message: "Email public@example.com or secret@example.com" },
|
|
174
|
-
{},
|
|
175
|
-
);
|
|
176
|
-
expect(result).toBeDefined();
|
|
177
|
-
const text = result!.message as string;
|
|
178
|
-
// public@example.com should be preserved, secret@example.com should be redacted
|
|
179
|
-
expect(text).toContain("public@example.com");
|
|
180
|
-
expect(text).not.toContain("secret@example.com");
|
|
181
|
-
expect(text).toContain("[EMAIL_1]");
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("uses mask redaction strategy", () => {
|
|
185
|
-
const config = makeConfig({ redactStrategy: "mask" });
|
|
186
|
-
const handler = createToolResultHandler(config, regexEngine);
|
|
187
|
-
const result = handler(
|
|
188
|
-
{ message: "SSN is 123-45-6789" },
|
|
189
|
-
{},
|
|
190
|
-
);
|
|
191
|
-
expect(result).toBeDefined();
|
|
192
|
-
const text = result!.message as string;
|
|
193
|
-
expect(text).toContain("***********");
|
|
194
|
-
expect(text).not.toContain("123-45-6789");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("uses hash redaction strategy", () => {
|
|
198
|
-
const config = makeConfig({ redactStrategy: "hash" });
|
|
199
|
-
const handler = createToolResultHandler(config, regexEngine);
|
|
200
|
-
const result = handler(
|
|
201
|
-
{ message: "SSN is 123-45-6789" },
|
|
202
|
-
{},
|
|
203
|
-
);
|
|
204
|
-
expect(result).toBeDefined();
|
|
205
|
-
const text = result!.message as string;
|
|
206
|
-
expect(text).toMatch(/\[SSN_[a-f0-9]{12}\]/);
|
|
207
|
-
expect(text).not.toContain("123-45-6789");
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it("applies entityActions config — all modes produce redaction", () => {
|
|
211
|
-
const config = makeConfig({
|
|
212
|
-
entityActions: { SSN: "block", EMAIL: "warn" },
|
|
213
|
-
});
|
|
214
|
-
const handler = createToolResultHandler(config, regexEngine);
|
|
215
|
-
const result = handler(
|
|
216
|
-
{ message: "SSN 123-45-6789, email john@example.com" },
|
|
217
|
-
{},
|
|
218
|
-
);
|
|
219
|
-
expect(result).toBeDefined();
|
|
220
|
-
const text = result!.message as string;
|
|
221
|
-
// Both block and warn modes produce span-level redaction in tool results
|
|
222
|
-
expect(text).toContain("[SSN_1]");
|
|
223
|
-
expect(text).toContain("[EMAIL_1]");
|
|
224
|
-
expect(text).not.toContain("123-45-6789");
|
|
225
|
-
expect(text).not.toContain("john@example.com");
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("preserves non-text content blocks", () => {
|
|
229
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
230
|
-
const result = handler(
|
|
231
|
-
{
|
|
232
|
-
message: {
|
|
233
|
-
content: [
|
|
234
|
-
{ type: "text", text: "SSN is 123-45-6789" },
|
|
235
|
-
{ type: "image", source: { data: "imagedata" } },
|
|
236
|
-
],
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{},
|
|
240
|
-
);
|
|
241
|
-
expect(result).toBeDefined();
|
|
242
|
-
const msg = result!.message as Record<string, unknown>;
|
|
243
|
-
const content = msg.content as Array<Record<string, unknown>>;
|
|
244
|
-
expect(content[0].text).toBe("SSN is [SSN_1]");
|
|
245
|
-
expect((content[1] as any).type).toBe("image");
|
|
246
|
-
expect((content[1] as any).source.data).toBe("imagedata");
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
describe("audit logging", () => {
|
|
250
|
-
it("emits audit log when auditEnabled and PII found", () => {
|
|
251
|
-
const config = makeConfig({ auditEnabled: true });
|
|
252
|
-
const logger = makeLogger();
|
|
253
|
-
const handler = createToolResultHandler(config, regexEngine, logger);
|
|
254
|
-
|
|
255
|
-
handler(
|
|
256
|
-
{ message: "SSN 123-45-6789", toolName: "file_read" },
|
|
257
|
-
{},
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
expect(logger.info).toHaveBeenCalledOnce();
|
|
261
|
-
const logCall = logger.info.mock.calls[0][0] as string;
|
|
262
|
-
expect(logCall).toContain("[FOGCLAW AUDIT]");
|
|
263
|
-
expect(logCall).toContain("tool_result_scan");
|
|
264
|
-
expect(logCall).toContain('"source":"tool_result"');
|
|
265
|
-
expect(logCall).toContain('"toolName":"file_read"');
|
|
266
|
-
expect(logCall).toContain('"SSN"');
|
|
267
|
-
// Must not contain raw PII
|
|
268
|
-
expect(logCall).not.toContain("123-45-6789");
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("does not emit audit log when auditEnabled is false", () => {
|
|
272
|
-
const config = makeConfig({ auditEnabled: false });
|
|
273
|
-
const logger = makeLogger();
|
|
274
|
-
const handler = createToolResultHandler(config, regexEngine, logger);
|
|
275
|
-
|
|
276
|
-
handler({ message: "SSN 123-45-6789" }, {});
|
|
277
|
-
|
|
278
|
-
expect(logger.info).not.toHaveBeenCalled();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("does not emit audit log when no PII found", () => {
|
|
282
|
-
const config = makeConfig({ auditEnabled: true });
|
|
283
|
-
const logger = makeLogger();
|
|
284
|
-
const handler = createToolResultHandler(config, regexEngine, logger);
|
|
285
|
-
|
|
286
|
-
handler({ message: "clean text" }, {});
|
|
287
|
-
|
|
288
|
-
expect(logger.info).not.toHaveBeenCalled();
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it("includes entity count and labels in audit log", () => {
|
|
292
|
-
const config = makeConfig({ auditEnabled: true });
|
|
293
|
-
const logger = makeLogger();
|
|
294
|
-
const handler = createToolResultHandler(config, regexEngine, logger);
|
|
295
|
-
|
|
296
|
-
handler(
|
|
297
|
-
{ message: "Call 555-123-4567, email john@example.com" },
|
|
298
|
-
{},
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
const logCall = logger.info.mock.calls[0][0] as string;
|
|
302
|
-
const parsed = JSON.parse(logCall.replace("[FOGCLAW AUDIT] tool_result_scan ", ""));
|
|
303
|
-
expect(parsed.totalEntities).toBe(2);
|
|
304
|
-
expect(parsed.labels).toContain("PHONE");
|
|
305
|
-
expect(parsed.labels).toContain("EMAIL");
|
|
306
|
-
expect(parsed.source).toBe("tool_result");
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it("handles multiple text blocks with PII in different blocks", () => {
|
|
311
|
-
const handler = createToolResultHandler(makeConfig(), regexEngine);
|
|
312
|
-
const result = handler(
|
|
313
|
-
{
|
|
314
|
-
message: {
|
|
315
|
-
content: [
|
|
316
|
-
{ type: "text", text: "First block clean" },
|
|
317
|
-
{ type: "text", text: "Second block SSN 123-45-6789" },
|
|
318
|
-
],
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
{},
|
|
322
|
-
);
|
|
323
|
-
expect(result).toBeDefined();
|
|
324
|
-
const msg = result!.message as Record<string, unknown>;
|
|
325
|
-
const content = msg.content as Array<Record<string, unknown>>;
|
|
326
|
-
expect(content[0].text).toBe("First block clean");
|
|
327
|
-
expect(content[1].text).toBe("Second block SSN [SSN_1]");
|
|
328
|
-
});
|
|
329
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"rootDir": "src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"declaration": true,
|
|
11
|
-
"declarationMap": true,
|
|
12
|
-
"sourceMap": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"skipLibCheck": true,
|
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
|
16
|
-
"resolveJsonModule": true
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*"],
|
|
19
|
-
"exclude": ["node_modules", "dist", "tests"]
|
|
20
|
-
}
|