@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
package/dist/scanner.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
+
import { canonicalType } from "./types.js";
|
|
1
2
|
import { RegexEngine } from "./engines/regex.js";
|
|
2
3
|
import { GlinerEngine } from "./engines/gliner.js";
|
|
4
|
+
function normalizeAllowlistValue(value) {
|
|
5
|
+
return value.trim().toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
function buildPatternMaps(value) {
|
|
8
|
+
if (!value || value.length === 0) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return value.map((pattern) => new RegExp(pattern, "i"));
|
|
12
|
+
}
|
|
3
13
|
export class Scanner {
|
|
4
14
|
regexEngine;
|
|
5
15
|
glinerEngine;
|
|
6
16
|
glinerAvailable = false;
|
|
7
17
|
config;
|
|
18
|
+
allowlist;
|
|
8
19
|
constructor(config) {
|
|
9
20
|
this.config = config;
|
|
10
21
|
this.regexEngine = new RegexEngine();
|
|
11
|
-
|
|
22
|
+
const glinerThreshold = this.computeGlinerThreshold(config);
|
|
23
|
+
this.glinerEngine = new GlinerEngine(config.model, glinerThreshold);
|
|
12
24
|
if (config.custom_entities.length > 0) {
|
|
13
25
|
this.glinerEngine.setCustomLabels(config.custom_entities);
|
|
14
26
|
}
|
|
27
|
+
this.allowlist = this.buildAllowlistCache(config.allowlist);
|
|
15
28
|
}
|
|
16
29
|
async initialize() {
|
|
17
30
|
try {
|
|
@@ -27,12 +40,14 @@ export class Scanner {
|
|
|
27
40
|
if (!text)
|
|
28
41
|
return { entities: [], text };
|
|
29
42
|
// Step 1: Regex pass (always runs, synchronous)
|
|
30
|
-
const regexEntities = this.regexEngine.scan(text);
|
|
43
|
+
const regexEntities = this.filterByPolicy(this.regexEngine.scan(text));
|
|
31
44
|
// Step 2: GLiNER pass (if available)
|
|
32
45
|
let glinerEntities = [];
|
|
33
46
|
if (this.glinerAvailable) {
|
|
34
47
|
try {
|
|
35
48
|
glinerEntities = await this.glinerEngine.scan(text, extraLabels);
|
|
49
|
+
glinerEntities = this.filterByConfidence(glinerEntities);
|
|
50
|
+
glinerEntities = this.filterByPolicy(glinerEntities);
|
|
36
51
|
}
|
|
37
52
|
catch (err) {
|
|
38
53
|
console.warn(`[fogclaw] GLiNER scan failed, using regex results only: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -42,6 +57,65 @@ export class Scanner {
|
|
|
42
57
|
const merged = deduplicateEntities([...regexEntities, ...glinerEntities]);
|
|
43
58
|
return { entities: merged, text };
|
|
44
59
|
}
|
|
60
|
+
filterByConfidence(entities) {
|
|
61
|
+
return entities.filter((entity) => {
|
|
62
|
+
const threshold = this.getThresholdForLabel(entity.label);
|
|
63
|
+
return entity.confidence >= threshold;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
filterByPolicy(entities) {
|
|
67
|
+
if (this.allowlist.values.size === 0 &&
|
|
68
|
+
this.allowlist.patterns.length === 0 &&
|
|
69
|
+
this.allowlist.entityValues.size === 0) {
|
|
70
|
+
return entities;
|
|
71
|
+
}
|
|
72
|
+
return entities.filter((entity) => !this.shouldAllowlistEntity(entity));
|
|
73
|
+
}
|
|
74
|
+
shouldAllowlistEntity(entity) {
|
|
75
|
+
const normalizedText = normalizeAllowlistValue(entity.text);
|
|
76
|
+
if (this.allowlist.values.has(normalizedText)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (this.allowlist.patterns.some((pattern) => pattern.test(entity.text))) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const entityValues = this.allowlist.entityValues.get(entity.label);
|
|
83
|
+
if (entityValues && entityValues.has(normalizedText)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
getThresholdForLabel(label) {
|
|
89
|
+
const canonicalLabel = canonicalType(label);
|
|
90
|
+
return this.config.entityConfidenceThresholds[canonicalLabel] ?? this.config.confidence_threshold;
|
|
91
|
+
}
|
|
92
|
+
computeGlinerThreshold(config) {
|
|
93
|
+
const thresholds = Object.values(config.entityConfidenceThresholds);
|
|
94
|
+
if (thresholds.length === 0) {
|
|
95
|
+
return config.confidence_threshold;
|
|
96
|
+
}
|
|
97
|
+
return Math.min(config.confidence_threshold, ...thresholds);
|
|
98
|
+
}
|
|
99
|
+
buildAllowlistCache(allowlist) {
|
|
100
|
+
const globalValues = new Set(allowlist.values.map((value) => normalizeAllowlistValue(value)));
|
|
101
|
+
const globalPatterns = buildPatternMaps(allowlist.patterns);
|
|
102
|
+
const entityValues = new Map();
|
|
103
|
+
for (const [entityType, values] of Object.entries(allowlist.entities)) {
|
|
104
|
+
const canonical = canonicalType(entityType);
|
|
105
|
+
const uniqueValues = values
|
|
106
|
+
.map((value) => normalizeAllowlistValue(value))
|
|
107
|
+
.filter((value) => value.length > 0);
|
|
108
|
+
entityValues.set(canonical, new Set(uniqueValues));
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
values: globalValues,
|
|
112
|
+
patterns: globalPatterns,
|
|
113
|
+
entityValues,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
get isGlinerAvailable() {
|
|
117
|
+
return this.glinerAvailable;
|
|
118
|
+
}
|
|
45
119
|
}
|
|
46
120
|
/**
|
|
47
121
|
* Remove overlapping entity spans. When two entities overlap,
|
package/dist/scanner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export interface Entity {
|
|
|
8
8
|
}
|
|
9
9
|
export type RedactStrategy = "token" | "mask" | "hash";
|
|
10
10
|
export type GuardrailAction = "redact" | "block" | "warn";
|
|
11
|
+
export interface EntityConfidenceThresholds {
|
|
12
|
+
[entityType: string]: number;
|
|
13
|
+
}
|
|
14
|
+
export interface EntityAllowlist {
|
|
15
|
+
values: string[];
|
|
16
|
+
patterns: string[];
|
|
17
|
+
entities: Record<string, string[]>;
|
|
18
|
+
}
|
|
11
19
|
export interface FogClawConfig {
|
|
12
20
|
enabled: boolean;
|
|
13
21
|
guardrail_mode: GuardrailAction;
|
|
@@ -16,6 +24,9 @@ export interface FogClawConfig {
|
|
|
16
24
|
confidence_threshold: number;
|
|
17
25
|
custom_entities: string[];
|
|
18
26
|
entityActions: Record<string, GuardrailAction>;
|
|
27
|
+
entityConfidenceThresholds: EntityConfidenceThresholds;
|
|
28
|
+
allowlist: EntityAllowlist;
|
|
29
|
+
auditEnabled: boolean;
|
|
19
30
|
}
|
|
20
31
|
export interface ScanResult {
|
|
21
32
|
entities: Entity[];
|
|
@@ -26,6 +37,11 @@ export interface RedactResult {
|
|
|
26
37
|
mapping: Record<string, string>;
|
|
27
38
|
entities: Entity[];
|
|
28
39
|
}
|
|
40
|
+
export interface GuardrailPlan {
|
|
41
|
+
blocked: Entity[];
|
|
42
|
+
warned: Entity[];
|
|
43
|
+
redacted: Entity[];
|
|
44
|
+
}
|
|
29
45
|
export declare const CANONICAL_TYPE_MAP: Record<string, string>;
|
|
30
46
|
export declare function canonicalType(entityType: string): string;
|
|
31
47
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC5B;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,eAAe,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC5B;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IACzC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,eAAe,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/C,0BAA0B,EAAE,0BAA0B,CAAC;IACvD,SAAS,EAAE,eAAe,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAYrD,CAAC;AAEF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGxD"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAqDA,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,SAAS;IACd,YAAY,EAAE,OAAO;IACrB,sBAAsB,EAAE,KAAK;IAC7B,kBAAkB,EAAE,aAAa;IACjC,aAAa,EAAE,MAAM;CACtB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IACnD,OAAO,kBAAkB,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;AACtD,CAAC"}
|
|
@@ -25,11 +25,11 @@ Users should be able to install and use FogClaw today from a DataFog-owned names
|
|
|
25
25
|
- [x] (2026-02-17T18:54:00Z) P3 [M1] Updated `package-lock` metadata and refreshed scope for build/release artifacts.
|
|
26
26
|
- [x] (2026-02-17T18:55:00Z) P4 [M2] Re-ran build/test/smoke + `npm pack --json` + `npm publish --dry-run` validations.
|
|
27
27
|
- [x] (2026-02-17T18:56:00Z) P5 [M2] Verified `openclaw plugins install` against the built `datafog-fogclaw-0.1.0.tgz` in a clean runtime; plugin now loads as `fogclaw` with status `loaded` and tools `fogclaw_scan, fogclaw_redact`.
|
|
28
|
-
- [
|
|
28
|
+
- [x] (2026-02-17T20:33:00Z) P5 [M2] Verified `openclaw plugins install @datafog/fogclaw` resolves to published `0.1.4` in this runtime.
|
|
29
29
|
- [x] (2026-02-17T19:27:00Z) P6 [M2] Fixed GLiNER startup blocker in Node by pinning `onnxruntime-web` to `1.21.0`, preventing `./webgpu` export resolution errors from `gliner` in OpenClaw install paths.
|
|
30
30
|
- [x] (2026-02-17T19:34:00Z) P6 [M2] Added direct `sharp` dependency `0.34.5` with an override to prevent optional sharp native install failure (`sharp-darwin-arm64v8.node` missing) during OpenClaw install-time dependency bootstrap.
|
|
31
|
-
- [
|
|
32
|
-
- [x] (2026-02-17T18:56:00Z) P6 [M3] Prepare and execute V1 publish/release of `@datafog/fogclaw
|
|
31
|
+
- [x] (2026-02-17T20:29:00Z) P6 [M3] Published the startup hardening update as `@datafog/fogclaw@0.1.4` (OTP already provisioned) and confirmed package availability.
|
|
32
|
+
- [x] (2026-02-17T18:56:00Z) P6 [M3] Prepare and execute V1 publish/release of `@datafog/fogclaw`.
|
|
33
33
|
- [x] (2026-02-17T18:57:00Z) P7 [M3] Capture release artifacts and update evidence notes; add follow-up for dependency install blocker in OpenClaw install path.
|
|
34
34
|
|
|
35
35
|
|
|
@@ -47,13 +47,15 @@ Users should be able to install and use FogClaw today from a DataFog-owned names
|
|
|
47
47
|
- Observation: The prior `TypeError: Cannot read properties of undefined (reading 'trim')` install failure was caused by OpenClaw's `registerTool` contract when tool objects omit a top-level `name`.
|
|
48
48
|
Evidence: `src/plugins/registry.ts` in OpenClaw (`registerTool` maps `tool.name` without null-guard); fixed in this repository by adding `name` fields to both tool objects.
|
|
49
49
|
|
|
50
|
-
- Observation: `openclaw plugins install @datafog/fogclaw`
|
|
51
|
-
Evidence: `npm view @datafog/fogclaw@0.1.4`
|
|
50
|
+
- Observation: `openclaw plugins install @datafog/fogclaw` now resolves and installs successfully from npm as version `0.1.4` in this environment.
|
|
51
|
+
Evidence: `npm view @datafog/fogclaw@0.1.4` returns `version = '0.1.4'` and `openclaw plugins install @datafog/fogclaw` reports success with plugin `fogclaw` loaded.
|
|
52
52
|
|
|
53
|
-
- Observation: GLiNER startup now avoids the `onnxruntime-web/webgpu` exports failure
|
|
54
|
-
Evidence:
|
|
53
|
+
- Observation: GLiNER startup now avoids the `onnxruntime-web/webgpu` exports failure after pinning `onnxruntime-web` to 1.21.0.
|
|
54
|
+
Evidence: in install flows, the earlier `./webgpu` subpath export error no longer blocks plugin registration in this environment.
|
|
55
55
|
- Observation: optional sharp runtime failures are now mitigated in clean install flows by pinning direct `sharp` 0.34.5; this removes the previously recurrent `Cannot find module '../build/Release/sharp-darwin-arm64v8.node'` warning in OpenClaw plugin install logs.
|
|
56
|
-
Evidence: `openclaw plugins install` from `datafog-fogclaw-0.1.4
|
|
56
|
+
Evidence: `openclaw plugins install` from `datafog-fogclaw-0.1.4` no longer emits that missing binary warning.
|
|
57
|
+
- Observation: Running `pnpm openclaw` from the local OpenClaw source tree can still emit duplicate-plugin warnings when both the source-bundled `fogclaw` extension and installed `~/.openclaw/extensions/fogclaw` are present.
|
|
58
|
+
Evidence: this is due discovery order (`global` then `bundled`) and not a packaging defect in `@datafog/fogclaw` when installed in a standard global runtime.
|
|
57
59
|
|
|
58
60
|
## Decision Log
|
|
59
61
|
|
|
@@ -83,9 +85,9 @@ Users should be able to install and use FogClaw today from a DataFog-owned names
|
|
|
83
85
|
- Local validation confirms namespace rename compiles and tests (`npm run build`, `npm run test`, `npm run test:plugin-smoke`) continue to pass.
|
|
84
86
|
- `npm pack --json` and `npm publish --dry-run` now emit scoped package metadata under `@datafog/fogclaw`.
|
|
85
87
|
- `openclaw plugins install` against a clean temporary state and local `datafog-fogclaw-0.1.0.tgz` now succeeds; `openclaw plugins info fogclaw` shows status `loaded` and tools `fogclaw_scan`, `fogclaw_redact`.
|
|
86
|
-
- `openclaw plugins install @datafog/fogclaw`
|
|
87
|
-
- GLiNER
|
|
88
|
-
- GLiNER startup now avoids the webgpu export resolution error after pinning `onnxruntime-web` to `1.21.0
|
|
88
|
+
- `openclaw plugins install @datafog/fogclaw` now resolves from npm (`@datafog/fogclaw@0.1.4`) and plugin info/list flows show `fogclaw` as loaded.
|
|
89
|
+
- GLiNER may still fallback to regex-only in some environments, but the webgpu export blocker no longer prevents install or plugin registration.
|
|
90
|
+
- GLiNER startup now avoids the webgpu export resolution error after pinning `onnxruntime-web` to `1.21.0` in this runtime; fallback behavior remains safe if ONNX still cannot initialize.
|
|
89
91
|
|
|
90
92
|
|
|
91
93
|
## Context and Orientation
|
|
@@ -246,7 +248,7 @@ Expect:
|
|
|
246
248
|
## Idempotence and Recovery
|
|
247
249
|
|
|
248
250
|
- Re-running namespace renames is safe if done as a single set of edits (`package.json`, `package-lock.json`, docs).
|
|
249
|
-
- If `openclaw plugins install @datafog/fogclaw` cannot run due stale install artifacts,
|
|
251
|
+
- If `openclaw plugins install @datafog/fogclaw` cannot run due stale install artifacts, remove the stale extension directory (`~/.openclaw/extensions/fogclaw`) and reinstall from the scoped spec in one flow, keeping the `plugins.entries.fogclaw` config entry intact.
|
|
250
252
|
- If `npm publish` is blocked, capture exact npm error + timestamp, resolve token/2FA/access, then rerun from step 5 onward.
|
|
251
253
|
- If the package publishes but install fails due manifest mismatch, roll back to previous package version in npm and fix manifest/docs before republishing.
|
|
252
254
|
|
|
@@ -273,13 +275,13 @@ Expect:
|
|
|
273
275
|
- `npm publish --dry-run` succeeded and produced scoped package manifest notice.
|
|
274
276
|
|
|
275
277
|
- Installability evidence:
|
|
276
|
-
- `openclaw plugins install
|
|
277
|
-
- `openclaw plugins
|
|
278
|
-
- GLiNER
|
|
278
|
+
- `openclaw plugins install @datafog/fogclaw` succeeds in this environment and installs the published `0.1.4` package.
|
|
279
|
+
- `openclaw plugins info fogclaw` and `openclaw plugins list | rg fogclaw` confirm plugin status `loaded` and tools `fogclaw_scan`, `fogclaw_redact`.
|
|
280
|
+
- GLiNER can return model-backed detections in supported runtimes; plugin registration remains reliable even when inference falls back to regex.
|
|
279
281
|
|
|
280
282
|
- `git rev-parse HEAD` (of implementation snapshot): capture before final merge.
|
|
281
283
|
|
|
282
|
-
|
|
284
|
+
|
|
283
285
|
|
|
284
286
|
|
|
285
287
|
## Interfaces and Dependencies
|
|
@@ -308,8 +310,8 @@ Expect:
|
|
|
308
310
|
- date: 2026-02-17T20:33:00Z
|
|
309
311
|
- open findings by priority (if any): pending
|
|
310
312
|
- evidence:
|
|
311
|
-
- installability
|
|
312
|
-
- scoped npm
|
|
313
|
+
- installability against clean runtime now succeeds for `openclaw plugins install @datafog/fogclaw` (version `0.1.4`) and `openclaw plugins info/list` confirms `fogclaw` plugin visibility.
|
|
314
|
+
- scoped npm package is published and discoverable in registry metadata.
|
|
313
315
|
- rollback: revert to previous working scoped package state (or keep changes in branch) if publish credentials/visibility unavailable
|
|
314
316
|
- post-release checks:
|
|
315
317
|
- `openclaw plugins install @datafog/fogclaw`
|
|
@@ -320,6 +322,7 @@ Expect:
|
|
|
320
322
|
## Revision Notes
|
|
321
323
|
|
|
322
324
|
- 2026-02-17T10:57:00Z: Initialized plan for V1 scoped-release path in `@datafog/fogclaw` and documented zero-logic-change constraints for immediate installability milestone.
|
|
323
|
-
- 2026-02-17T18:57:00Z: Completed namespace migration in package metadata and install/docs (`package.json`, `package-lock.json`, `README.md`, `docs/plugins/fogclaw.md`). Ran full local validation (`npm run build`, `npm run test`, `npm run test:plugin-smoke`, `npm pack --json`, `npm publish --dry-run`) and
|
|
324
|
-
- 2026-02-17T19:08:00Z: Fixed OpenClaw compatibility in `src/index.ts` by adding explicit `name` fields to `fogclaw_scan` and `fogclaw_redact` tool registrations to avoid undefined `.trim()` during registration; verified clean-runtime
|
|
325
|
-
- 2026-02-17T20:31:00Z: Added explicit `modelType: "span-level"` for GLiNER runtime configuration
|
|
325
|
+
- 2026-02-17T18:57:00Z: Completed namespace migration in package metadata and install/docs (`package.json`, `package-lock.json`, `README.md`, `docs/plugins/fogclaw.md`). Ran full local validation (`npm run build`, `npm run test`, `npm run test:plugin-smoke`, `npm pack --json`, `npm publish --dry-run`) and prepared release notes.
|
|
326
|
+
- 2026-02-17T19:08:00Z: Fixed OpenClaw compatibility in `src/index.ts` by adding explicit `name` fields to `fogclaw_scan` and `fogclaw_redact` tool registrations to avoid undefined `.trim()` during registration; verified `openclaw plugins install` clean-runtime success with local tarball and published package commands (`openclaw plugins install <tgz>/<scoped spec>`, `plugins info`, `plugins list`).
|
|
327
|
+
- 2026-02-17T20:31:00Z: Added explicit `modelType: "span-level"` for GLiNER runtime configuration and pinned runtime dependencies (`onnxruntime-web`/`sharp`) so local OpenClaw install path no longer fails at startup from these blockers.
|
|
328
|
+
- 2026-02-17T20:33:00Z: Confirmed `@datafog/fogclaw@0.1.4` is published and installable via `openclaw plugins install @datafog/fogclaw` in this environment.
|
package/docs/plugins/fogclaw.md
CHANGED
|
@@ -27,6 +27,8 @@ openclaw plugins install @datafog/fogclaw
|
|
|
27
27
|
|
|
28
28
|
After install, restart the Gateway and enable/configure `plugins.entries.fogclaw`.
|
|
29
29
|
|
|
30
|
+
A moderately technical user can also manage this in the OpenClaw **Control UI** (`openclaw dashboard`) under Config; the plugin schema now exposes labeled fields for `guardrail_mode`, `redactStrategy`, and related policy options in the UI form.
|
|
31
|
+
|
|
30
32
|
## Plugin entry
|
|
31
33
|
|
|
32
34
|
The package exports the plugin manifest and entry as:
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
"redactStrategy": "token",
|
|
5
5
|
"model": "onnx-community/gliner_large-v2.1",
|
|
6
6
|
"confidence_threshold": 0.5,
|
|
7
|
+
"entityConfidenceThresholds": {
|
|
8
|
+
"PERSON": 0.6,
|
|
9
|
+
"ORGANIZATION": 0.7
|
|
10
|
+
},
|
|
7
11
|
"custom_entities": ["project codename", "internal tool name"],
|
|
8
12
|
"entityActions": {
|
|
9
13
|
"SSN": "block",
|
|
@@ -11,5 +15,19 @@
|
|
|
11
15
|
"EMAIL": "redact",
|
|
12
16
|
"PHONE": "redact",
|
|
13
17
|
"PERSON": "warn"
|
|
14
|
-
}
|
|
18
|
+
},
|
|
19
|
+
"allowlist": {
|
|
20
|
+
"values": [
|
|
21
|
+
"noreply@example.com"
|
|
22
|
+
],
|
|
23
|
+
"patterns": [
|
|
24
|
+
"^internal-"
|
|
25
|
+
],
|
|
26
|
+
"entities": {
|
|
27
|
+
"PERSON": [
|
|
28
|
+
"john doe"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"auditEnabled": true
|
|
15
33
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "fogclaw",
|
|
3
3
|
"name": "FogClaw",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "PII detection & custom entity redaction powered by DataFog",
|
|
4
|
+
"version": "0.1.6",
|
|
5
|
+
"description": "PII detection & custom entity redaction plugin powered by DataFog",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
|
-
"enabled": {
|
|
9
|
+
"enabled": {
|
|
10
|
+
"type": "boolean",
|
|
11
|
+
"default": true
|
|
12
|
+
},
|
|
10
13
|
"guardrail_mode": {
|
|
11
14
|
"type": "string",
|
|
12
15
|
"enum": ["redact", "block", "warn"],
|
|
@@ -27,9 +30,20 @@
|
|
|
27
30
|
"minimum": 0,
|
|
28
31
|
"maximum": 1
|
|
29
32
|
},
|
|
33
|
+
"entityConfidenceThresholds": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": {
|
|
36
|
+
"type": "number",
|
|
37
|
+
"minimum": 0,
|
|
38
|
+
"maximum": 1
|
|
39
|
+
},
|
|
40
|
+
"default": {}
|
|
41
|
+
},
|
|
30
42
|
"custom_entities": {
|
|
31
43
|
"type": "array",
|
|
32
|
-
"items": {
|
|
44
|
+
"items": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
33
47
|
"default": []
|
|
34
48
|
},
|
|
35
49
|
"entityActions": {
|
|
@@ -39,7 +53,92 @@
|
|
|
39
53
|
"enum": ["redact", "block", "warn"]
|
|
40
54
|
},
|
|
41
55
|
"default": {}
|
|
56
|
+
},
|
|
57
|
+
"allowlist": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"values": {
|
|
61
|
+
"type": "array",
|
|
62
|
+
"items": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"default": []
|
|
66
|
+
},
|
|
67
|
+
"patterns": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"items": {
|
|
70
|
+
"type": "string"
|
|
71
|
+
},
|
|
72
|
+
"default": []
|
|
73
|
+
},
|
|
74
|
+
"entities": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"additionalProperties": {
|
|
77
|
+
"type": "array",
|
|
78
|
+
"items": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"default": {
|
|
85
|
+
"values": [],
|
|
86
|
+
"patterns": [],
|
|
87
|
+
"entities": {}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"auditEnabled": {
|
|
91
|
+
"type": "boolean",
|
|
92
|
+
"default": true
|
|
42
93
|
}
|
|
43
94
|
}
|
|
95
|
+
},
|
|
96
|
+
"uiHints": {
|
|
97
|
+
"enabled": {
|
|
98
|
+
"label": "Enable FogClaw",
|
|
99
|
+
"help": "Enable or disable the plugin without uninstalling it."
|
|
100
|
+
},
|
|
101
|
+
"guardrail_mode": {
|
|
102
|
+
"label": "Guardrail Mode",
|
|
103
|
+
"help": "Default action for detections: redact, block, or warn."
|
|
104
|
+
},
|
|
105
|
+
"redactStrategy": {
|
|
106
|
+
"label": "Redaction Strategy",
|
|
107
|
+
"help": "How sensitive values are replaced in message context before the agent sees them."
|
|
108
|
+
},
|
|
109
|
+
"model": {
|
|
110
|
+
"label": "GLiNER Model",
|
|
111
|
+
"help": "Model used for zero-shot detection (defaults to onnx-community/gliner_large-v2.1).",
|
|
112
|
+
"advanced": true
|
|
113
|
+
},
|
|
114
|
+
"confidence_threshold": {
|
|
115
|
+
"label": "Confidence Threshold",
|
|
116
|
+
"help": "Minimum GLiNER score (0-1) required before an entity is treated as a detection.",
|
|
117
|
+
"advanced": true
|
|
118
|
+
},
|
|
119
|
+
"entityConfidenceThresholds": {
|
|
120
|
+
"label": "Per-Entity Confidence Thresholds",
|
|
121
|
+
"help": "Override confidence thresholds by entity label (for example: {\"PERSON\": 0.95, \"ORGANIZATION\": 0.7}).",
|
|
122
|
+
"advanced": true
|
|
123
|
+
},
|
|
124
|
+
"custom_entities": {
|
|
125
|
+
"label": "Custom Entity Labels",
|
|
126
|
+
"help": "Extra labels to detect as sensitive entities (for example: `project code`, `competitor name`)."
|
|
127
|
+
},
|
|
128
|
+
"entityActions": {
|
|
129
|
+
"label": "Entity Actions",
|
|
130
|
+
"help": "Map specific entity labels to per-entity behavior (for example: {\"EMAIL\": \"block\", \"PHONE\": \"redact\"}).",
|
|
131
|
+
"advanced": true
|
|
132
|
+
},
|
|
133
|
+
"allowlist": {
|
|
134
|
+
"label": "Allowlist / Exemptions",
|
|
135
|
+
"help": "Global and per-entity allowlist entries to exclude from enforcement. Supports exact values and regex patterns.",
|
|
136
|
+
"advanced": true
|
|
137
|
+
},
|
|
138
|
+
"auditEnabled": {
|
|
139
|
+
"label": "Audit Logging",
|
|
140
|
+
"help": "Emit structured audit summaries for guardrail decisions.",
|
|
141
|
+
"advanced": true
|
|
142
|
+
}
|
|
44
143
|
}
|
|
45
144
|
}
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -1,8 +1,109 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
canonicalType,
|
|
3
|
+
type EntityAllowlist,
|
|
4
|
+
type FogClawConfig,
|
|
5
|
+
type GuardrailAction,
|
|
6
|
+
type RedactStrategy,
|
|
7
|
+
} from "./types.js";
|
|
2
8
|
|
|
3
9
|
const VALID_GUARDRAIL_MODES: GuardrailAction[] = ["redact", "block", "warn"];
|
|
4
10
|
const VALID_REDACT_STRATEGIES: RedactStrategy[] = ["token", "mask", "hash"];
|
|
5
11
|
|
|
12
|
+
function ensureStringList(value: unknown, path: string): string[] {
|
|
13
|
+
if (!Array.isArray(value)) {
|
|
14
|
+
throw new Error(`${path} must be an array of strings`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const entries = value.filter((entry): entry is string => {
|
|
18
|
+
if (typeof entry !== "string") {
|
|
19
|
+
throw new Error(`${path} must contain only strings`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureEntityAllowlist(value: unknown): EntityAllowlist {
|
|
29
|
+
if (value == null) {
|
|
30
|
+
return { values: [], patterns: [], entities: {} };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
34
|
+
throw new Error("allowlist must be an object");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const raw = value as Record<string, unknown>;
|
|
38
|
+
const values = ensureStringList(raw.values ?? [], "allowlist.values");
|
|
39
|
+
const patterns = ensureStringList(raw.patterns ?? [], "allowlist.patterns");
|
|
40
|
+
|
|
41
|
+
for (const pattern of patterns) {
|
|
42
|
+
try {
|
|
43
|
+
new RegExp(pattern);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(`allowlist.patterns contains invalid regex pattern: "${pattern}"`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const entitiesValue = raw.entities ?? {};
|
|
50
|
+
if (
|
|
51
|
+
typeof entitiesValue !== "object" ||
|
|
52
|
+
Array.isArray(entitiesValue) ||
|
|
53
|
+
entitiesValue === null
|
|
54
|
+
) {
|
|
55
|
+
throw new Error("allowlist.entities must be an object mapping entity labels to string arrays");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const entities: Record<string, string[]> = {};
|
|
59
|
+
for (const [entityType, entryValue] of Object.entries(entitiesValue)) {
|
|
60
|
+
const normalizedType = canonicalType(entityType);
|
|
61
|
+
entities[normalizedType] = ensureStringList(entryValue, `allowlist.entities.${entityType}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
values: [...new Set(values)],
|
|
66
|
+
patterns: [...new Set(patterns)],
|
|
67
|
+
entities,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureEntityConfidenceThresholds(
|
|
72
|
+
value: unknown,
|
|
73
|
+
): Record<string, number> {
|
|
74
|
+
if (!value) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof value !== "object" || Array.isArray(value) || value === null) {
|
|
79
|
+
throw new Error("entityConfidenceThresholds must be an object");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const raw = value as Record<string, unknown>;
|
|
83
|
+
const normalized: Record<string, number> = {};
|
|
84
|
+
|
|
85
|
+
for (const [entityType, rawThreshold] of Object.entries(raw)) {
|
|
86
|
+
if (typeof rawThreshold !== "number" || Number.isNaN(rawThreshold)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`entityConfidenceThresholds["${entityType}"] must be a number between 0 and 1, got ${String(
|
|
89
|
+
rawThreshold,
|
|
90
|
+
)}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (rawThreshold < 0 || rawThreshold > 1) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`entityConfidenceThresholds["${entityType}"] must be between 0 and 1, got ${rawThreshold}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const canonicalTypeKey = canonicalType(entityType);
|
|
101
|
+
normalized[canonicalTypeKey] = rawThreshold;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return normalized;
|
|
105
|
+
}
|
|
106
|
+
|
|
6
107
|
export const DEFAULT_CONFIG: FogClawConfig = {
|
|
7
108
|
enabled: true,
|
|
8
109
|
guardrail_mode: "redact",
|
|
@@ -11,10 +112,37 @@ export const DEFAULT_CONFIG: FogClawConfig = {
|
|
|
11
112
|
confidence_threshold: 0.5,
|
|
12
113
|
custom_entities: [],
|
|
13
114
|
entityActions: {},
|
|
115
|
+
entityConfidenceThresholds: {},
|
|
116
|
+
allowlist: {
|
|
117
|
+
values: [],
|
|
118
|
+
patterns: [],
|
|
119
|
+
entities: {},
|
|
120
|
+
},
|
|
121
|
+
auditEnabled: true,
|
|
14
122
|
};
|
|
15
123
|
|
|
16
124
|
export function loadConfig(overrides: Partial<FogClawConfig>): FogClawConfig {
|
|
17
|
-
const config: FogClawConfig = {
|
|
125
|
+
const config: FogClawConfig = {
|
|
126
|
+
...DEFAULT_CONFIG,
|
|
127
|
+
...overrides,
|
|
128
|
+
entityActions: {
|
|
129
|
+
...DEFAULT_CONFIG.entityActions,
|
|
130
|
+
...(overrides.entityActions ?? {}),
|
|
131
|
+
},
|
|
132
|
+
entityConfidenceThresholds: {
|
|
133
|
+
...DEFAULT_CONFIG.entityConfidenceThresholds,
|
|
134
|
+
...(overrides.entityConfidenceThresholds ?? {}),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
config.allowlist = ensureEntityAllowlist(overrides.allowlist ?? DEFAULT_CONFIG.allowlist);
|
|
139
|
+
config.entityConfidenceThresholds = ensureEntityConfidenceThresholds(
|
|
140
|
+
config.entityConfidenceThresholds,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (typeof config.enabled !== "boolean") {
|
|
144
|
+
throw new Error(`enabled must be true or false`);
|
|
145
|
+
}
|
|
18
146
|
|
|
19
147
|
if (!VALID_GUARDRAIL_MODES.includes(config.guardrail_mode)) {
|
|
20
148
|
throw new Error(
|
|
@@ -34,13 +162,22 @@ export function loadConfig(overrides: Partial<FogClawConfig>): FogClawConfig {
|
|
|
34
162
|
);
|
|
35
163
|
}
|
|
36
164
|
|
|
165
|
+
if (typeof config.auditEnabled !== "boolean") {
|
|
166
|
+
throw new Error(`auditEnabled must be true or false`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const normalizedActions: Record<string, GuardrailAction> = {};
|
|
37
170
|
for (const [entityType, action] of Object.entries(config.entityActions)) {
|
|
38
171
|
if (!VALID_GUARDRAIL_MODES.includes(action)) {
|
|
39
172
|
throw new Error(
|
|
40
173
|
`Invalid action "${action}" for entity type "${entityType}". Must be one of: ${VALID_GUARDRAIL_MODES.join(", ")}`,
|
|
41
174
|
);
|
|
42
175
|
}
|
|
176
|
+
|
|
177
|
+
const normalizedType = canonicalType(entityType);
|
|
178
|
+
normalizedActions[normalizedType] = action;
|
|
43
179
|
}
|
|
180
|
+
config.entityActions = normalizedActions;
|
|
44
181
|
|
|
45
182
|
return config;
|
|
46
183
|
}
|