@datafog/fogclaw 0.1.5 → 0.2.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +83 -4
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +100 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/extract.d.ts +28 -0
  8. package/dist/extract.d.ts.map +1 -0
  9. package/dist/extract.js +91 -0
  10. package/dist/extract.js.map +1 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +135 -30
  14. package/dist/index.js.map +1 -1
  15. package/dist/message-sending-handler.d.ts +40 -0
  16. package/dist/message-sending-handler.d.ts.map +1 -0
  17. package/dist/message-sending-handler.js +50 -0
  18. package/dist/message-sending-handler.js.map +1 -0
  19. package/dist/scanner.d.ts +13 -2
  20. package/dist/scanner.d.ts.map +1 -1
  21. package/dist/scanner.js +76 -2
  22. package/dist/scanner.js.map +1 -1
  23. package/dist/tool-result-handler.d.ts +36 -0
  24. package/dist/tool-result-handler.d.ts.map +1 -0
  25. package/dist/tool-result-handler.js +91 -0
  26. package/dist/tool-result-handler.js.map +1 -0
  27. package/dist/types.d.ts +17 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/types.js +3 -0
  30. package/dist/types.js.map +1 -1
  31. package/docs/OBSERVABILITY.md +22 -15
  32. package/docs/SECURITY.md +6 -4
  33. package/docs/plans/active/2026-02-17-feat-tool-result-pii-scanning-plan.md +293 -0
  34. package/docs/specs/2026-02-17-feat-outbound-message-pii-scanning-spec.md +93 -0
  35. package/docs/specs/2026-02-17-feat-tool-result-pii-scanning-spec.md +122 -0
  36. package/fogclaw.config.example.json +19 -1
  37. package/openclaw.plugin.json +63 -2
  38. package/package.json +9 -9
  39. package/scripts/ci/he-docs-drift.sh +0 -0
  40. package/scripts/ci/he-docs-lint.sh +0 -0
  41. package/scripts/ci/he-plans-lint.sh +0 -0
  42. package/scripts/ci/he-runbooks-lint.sh +0 -0
  43. package/scripts/ci/he-specs-lint.sh +0 -0
  44. package/scripts/ci/he-spikes-lint.sh +0 -0
  45. package/scripts/runbooks/select-runbooks.sh +0 -0
  46. package/src/config.ts +139 -2
  47. package/src/extract.ts +98 -0
  48. package/src/index.ts +194 -36
  49. package/src/message-sending-handler.ts +87 -0
  50. package/src/scanner.ts +114 -8
  51. package/src/tool-result-handler.ts +133 -0
  52. package/src/types.ts +23 -0
  53. package/tests/config.test.ts +55 -81
  54. package/tests/extract.test.ts +185 -0
  55. package/tests/message-sending-handler.test.ts +244 -0
  56. package/tests/plugin-smoke.test.ts +139 -3
  57. package/tests/scanner.test.ts +61 -1
  58. package/tests/tool-result-handler.test.ts +329 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Added
6
+
7
+ - **Tool result PII scanning** — new `tool_result_persist` hook scans file reads, API responses, and web fetches for PII before they enter the session transcript. Uses regex engine (sync) for <1ms latency on the hot path.
8
+ - **Outbound message PII scanning** — new `message_sending` hook provides a last-chance gate that catches PII in agent replies before delivery to Telegram, Slack, Discord, and other external channels. Uses full Scanner (regex + GLiNER).
9
+ - **Policy allowlist** — whitelist exact strings, regex patterns, or per-entity-type values to skip enforcement on known-safe content (e.g., `noreply@example.com`).
10
+ - **Per-entity confidence thresholds** — tune GLiNER detection sensitivity per label (e.g., require 0.7 for PERSON, 0.85 for ORGANIZATION).
11
+ - **Audit trail logging** — structured audit log entries with `source` field (`guardrail`, `tool_result`, `outbound`) when `auditEnabled: true`. Logs entity counts and labels without raw PII.
12
+ - **Policy preview tool** — `fogclaw_preview` shows which entities would be blocked, warned, or redacted without changing runtime behavior.
13
+ - **Scanning architecture documentation** — README now includes a comparison matrix showing engine trade-offs across all three hooks.
14
+ - **Control UI hints** — `openclaw.plugin.json` includes `uiHints` for policy configuration in OpenClaw's Control UI.
15
+
16
+ ### Changed
17
+
18
+ - `resolveAction` extracted to shared `types.ts` module (was duplicated across files).
19
+ - README updated with three-layer scanning overview and defense-in-depth flow diagram.
20
+
21
+ ## 0.1.6
22
+
23
+ Initial scoped release as `@datafog/fogclaw`.
24
+
25
+ - Dual-engine PII detection (regex + GLiNER via ONNX)
26
+ - `before_agent_start` hook for automatic prompt guardrail
27
+ - `fogclaw_scan` and `fogclaw_redact` tools
28
+ - Configurable per-entity actions (`redact`, `block`, `warn`)
29
+ - Multiple redaction strategies (`token`, `mask`, `hash`)
30
+ - Custom entity types via GLiNER zero-shot NER
31
+ - Graceful degradation to regex-only when GLiNER unavailable
package/README.md CHANGED
@@ -6,12 +6,19 @@ FogClaw uses a dual-engine approach: battle-tested regex patterns for structured
6
6
 
7
7
  ## Features
8
8
 
9
+ - **Three-layer scanning** — inbound prompts, tool results, and outbound messages are all scanned for PII before they cross trust boundaries
9
10
  - **Automatic guardrail** — intercepts messages before they reach the LLM via OpenClaw's `before_agent_start` hook
10
- - **On-demand tools** — `fogclaw_scan` and `fogclaw_redact` tools the agent can invoke explicitly
11
+ - **Tool result scanning** — redacts PII in file reads, API responses, and web fetches before they enter the session transcript (`tool_result_persist`)
12
+ - **Outbound message scanning** — last-chance gate that catches PII in agent replies before delivery to external channels (`message_sending`)
13
+ - **On-demand tools** — `fogclaw_scan`, `fogclaw_preview`, and `fogclaw_redact`
11
14
  - **Dual detection engine** — regex for structured PII (<1ms), GLiNER for zero-shot NER (~50-200ms)
12
15
  - **Custom entity types** — define any entity label (e.g., "project codename", "competitor name") and GLiNER detects them with zero training
13
16
  - **Configurable actions** — per-entity-type behavior: `redact`, `block`, or `warn`
17
+ - **Per-entity confidence tuning** — tighten or relax detection confidence by label
18
+ - **Policy allowlist** — whitelist exact strings or regex patterns to skip enforcement on known-safe values
19
+ - **Policy preview** — run a dry-run simulation before changing runtime policy
14
20
  - **Multiple redaction strategies** — `token`, `mask`, or `hash`
21
+ - **Audit trail summary logging** — optional structured action summaries without logging raw entity content
15
22
  - **Graceful degradation** — falls back to regex-only mode if GLiNER fails to load
16
23
 
17
24
  ## Installation
@@ -60,6 +67,10 @@ cp fogclaw.config.example.json fogclaw.config.json
60
67
  "redactStrategy": "token",
61
68
  "model": "onnx-community/gliner_large-v2.1",
62
69
  "confidence_threshold": 0.5,
70
+ "entityConfidenceThresholds": {
71
+ "PERSON": 0.6,
72
+ "ORGANIZATION": 0.7
73
+ },
63
74
  "custom_entities": ["project codename", "competitor name"],
64
75
  "entityActions": {
65
76
  "SSN": "block",
@@ -67,7 +78,15 @@ cp fogclaw.config.example.json fogclaw.config.json
67
78
  "EMAIL": "redact",
68
79
  "PHONE": "redact",
69
80
  "PERSON": "warn"
70
- }
81
+ },
82
+ "allowlist": {
83
+ "values": ["noreply@example.com"],
84
+ "patterns": ["^internal-"],
85
+ "entities": {
86
+ "PERSON": ["john doe"]
87
+ }
88
+ },
89
+ "auditEnabled": true
71
90
  }
72
91
  ```
73
92
 
@@ -113,18 +132,54 @@ Incoming message
113
132
  +-----------+
114
133
  | GLiNER | persons, orgs, locations + your custom entities
115
134
  | (ONNX) | confidence: 0.0-1.0
116
- +-----+-----+
135
+ +-----------+
117
136
  |
118
137
  v
119
138
  +-----------+
120
139
  | Merge & | deduplicate overlapping spans, prefer higher confidence
121
140
  | Normalize |
122
- +-----+-----+
141
+ +-----------+
123
142
  |
124
143
  v
125
144
  Apply action per entity type (redact / block / warn)
126
145
  ```
127
146
 
147
+ ## Scanning Architecture
148
+
149
+ FogClaw hooks into three points in the OpenClaw message lifecycle. Each hook uses the detection engine best suited to its runtime constraints:
150
+
151
+ | Hook | Direction | Engine | Latency | Async | Entity Coverage |
152
+ |------|-----------|--------|---------|-------|-----------------|
153
+ | `before_agent_start` | Inbound (user prompt) | Regex + GLiNER | ~50-200ms | Yes | Full — structured PII + names, orgs, custom entities |
154
+ | `tool_result_persist` | Internal (tool results) | Regex only | <1ms | No (sync) | Structured PII — emails, SSNs, phones, credit cards, IPs |
155
+ | `message_sending` | Outbound (agent reply) | Regex + GLiNER | ~50-200ms | Yes | Full — structured PII + names, orgs, custom entities |
156
+
157
+ **Why regex-only for tool results?** OpenClaw's `tool_result_persist` hook requires synchronous handlers — async returns are rejected. GLiNER inference runs a synchronous ONNX native call that blocks the event loop for 100-500ms per invocation, which would degrade gateway responsiveness (delayed heartbeats, WebSocket pings, HTTP responses). Regex covers the high-confidence structured patterns most common in tool output (credentials in file reads, contact info in API responses). Person names and organization names are caught on the async inbound and outbound paths, providing defense-in-depth without hot-path latency.
158
+
159
+ ```
160
+ User prompt ──► before_agent_start (regex + GLiNER)
161
+
162
+
163
+ Agent + LLM
164
+
165
+ ┌─────────┼─────────┐
166
+ ▼ ▼ ▼
167
+ Tool call Tool call Tool call
168
+ │ │ │
169
+ ▼ ▼ ▼
170
+ tool_result_persist (regex only, sync)
171
+
172
+
173
+ Agent reply
174
+
175
+
176
+ message_sending (regex + GLiNER)
177
+
178
+
179
+ External channel
180
+ (Telegram, Slack, etc.)
181
+ ```
182
+
128
183
  ## Detected Entity Types
129
184
 
130
185
  ### Regex Engine (structured PII)
@@ -162,8 +217,11 @@ Plus any labels you add via `custom_entities` in the config.
162
217
  | `redactStrategy` | `string` | `"token"` | How to redact: `"token"`, `"mask"`, or `"hash"` |
163
218
  | `model` | `string` | `"onnx-community/gliner_large-v2.1"` | HuggingFace model path for GLiNER (or a local `.onnx` path for advanced setups). |
164
219
  | `confidence_threshold` | `number` | `0.5` | Minimum confidence for GLiNER detections (0-1) |
220
+ | `entityConfidenceThresholds` | `object` | `{}` | Per-label confidence overrides, e.g. `{ "PERSON": 0.7, "ORGANIZATION": 0.85 }` |
165
221
  | `custom_entities` | `string[]` | `[]` | Custom entity labels for zero-shot detection |
166
222
  | `entityActions` | `object` | `{}` | Per-entity-type action overrides |
223
+ | `allowlist` | `object` | `{}` | Exception rules to skip enforcement via exact values or regex patterns |
224
+ | `auditEnabled` | `boolean` | `true` | Emit structured audit logs for guardrail decisions |
167
225
 
168
226
  ## OpenClaw Tools
169
227
 
@@ -175,6 +233,21 @@ Scan text for PII and custom entities. Returns detected entities with types, pos
175
233
  - `text` (required) — text to scan
176
234
  - `custom_labels` (optional) — additional entity labels for zero-shot detection
177
235
 
236
+ ### `fogclaw_preview`
237
+
238
+ Preview what the guardrail would do for a message.
239
+
240
+ **Parameters:**
241
+ - `text` (required) — text to simulate
242
+ - `strategy` (optional) — `"token"`, `"mask"`, or `"hash"` (defaults to config)
243
+ - `custom_labels` (optional) — additional entity labels for zero-shot detection
244
+
245
+ **Response:**
246
+ - `entities`: detected entities and metadata
247
+ - `totalEntities`: total entities found
248
+ - `actionPlan`: counts and labels grouped by `blocked`, `warned`, `redacted`
249
+ - `redactedText`: message with only redacted entities applied
250
+
178
251
  ### `fogclaw_redact`
179
252
 
180
253
  Scan and redact PII/custom entities from text. Returns sanitized text with entities replaced.
@@ -219,6 +292,12 @@ npm run build # compile TypeScript
219
292
  npm run lint # type-check without emitting
220
293
  ```
221
294
 
295
+ ## Security Notes
296
+
297
+ - Keep `api.logger` output free of raw sensitive values.
298
+ - Use allowlists and `auditEnabled` according to your governance requirements.
299
+ - Consider `block` actions for high-risk entity types in regulated environments.
300
+
222
301
  ## License
223
302
 
224
303
  MIT
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FogClawConfig } from "./types.js";
1
+ import { type FogClawConfig } from "./types.js";
2
2
  export declare const DEFAULT_CONFIG: FogClawConfig;
3
3
  export declare function loadConfig(overrides: Partial<FogClawConfig>): FogClawConfig;
4
4
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAmC,MAAM,YAAY,CAAC;AAKjF,eAAO,MAAM,cAAc,EAAE,aAQ5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CA8B3E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAGnB,MAAM,YAAY,CAAC;AAoGpB,eAAO,MAAM,cAAc,EAAE,aAe5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CA2D3E"}
package/dist/config.js CHANGED
@@ -1,5 +1,74 @@
1
+ import { canonicalType, } from "./types.js";
1
2
  const VALID_GUARDRAIL_MODES = ["redact", "block", "warn"];
2
3
  const VALID_REDACT_STRATEGIES = ["token", "mask", "hash"];
4
+ function ensureStringList(value, path) {
5
+ if (!Array.isArray(value)) {
6
+ throw new Error(`${path} must be an array of strings`);
7
+ }
8
+ const entries = value.filter((entry) => {
9
+ if (typeof entry !== "string") {
10
+ throw new Error(`${path} must contain only strings`);
11
+ }
12
+ return true;
13
+ });
14
+ return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
15
+ }
16
+ function ensureEntityAllowlist(value) {
17
+ if (value == null) {
18
+ return { values: [], patterns: [], entities: {} };
19
+ }
20
+ if (typeof value !== "object" || Array.isArray(value)) {
21
+ throw new Error("allowlist must be an object");
22
+ }
23
+ const raw = value;
24
+ const values = ensureStringList(raw.values ?? [], "allowlist.values");
25
+ const patterns = ensureStringList(raw.patterns ?? [], "allowlist.patterns");
26
+ for (const pattern of patterns) {
27
+ try {
28
+ new RegExp(pattern);
29
+ }
30
+ catch {
31
+ throw new Error(`allowlist.patterns contains invalid regex pattern: "${pattern}"`);
32
+ }
33
+ }
34
+ const entitiesValue = raw.entities ?? {};
35
+ if (typeof entitiesValue !== "object" ||
36
+ Array.isArray(entitiesValue) ||
37
+ entitiesValue === null) {
38
+ throw new Error("allowlist.entities must be an object mapping entity labels to string arrays");
39
+ }
40
+ const entities = {};
41
+ for (const [entityType, entryValue] of Object.entries(entitiesValue)) {
42
+ const normalizedType = canonicalType(entityType);
43
+ entities[normalizedType] = ensureStringList(entryValue, `allowlist.entities.${entityType}`);
44
+ }
45
+ return {
46
+ values: [...new Set(values)],
47
+ patterns: [...new Set(patterns)],
48
+ entities,
49
+ };
50
+ }
51
+ function ensureEntityConfidenceThresholds(value) {
52
+ if (!value) {
53
+ return {};
54
+ }
55
+ if (typeof value !== "object" || Array.isArray(value) || value === null) {
56
+ throw new Error("entityConfidenceThresholds must be an object");
57
+ }
58
+ const raw = value;
59
+ const normalized = {};
60
+ for (const [entityType, rawThreshold] of Object.entries(raw)) {
61
+ if (typeof rawThreshold !== "number" || Number.isNaN(rawThreshold)) {
62
+ throw new Error(`entityConfidenceThresholds["${entityType}"] must be a number between 0 and 1, got ${String(rawThreshold)}`);
63
+ }
64
+ if (rawThreshold < 0 || rawThreshold > 1) {
65
+ throw new Error(`entityConfidenceThresholds["${entityType}"] must be between 0 and 1, got ${rawThreshold}`);
66
+ }
67
+ const canonicalTypeKey = canonicalType(entityType);
68
+ normalized[canonicalTypeKey] = rawThreshold;
69
+ }
70
+ return normalized;
71
+ }
3
72
  export const DEFAULT_CONFIG = {
4
73
  enabled: true,
5
74
  guardrail_mode: "redact",
@@ -8,9 +77,32 @@ export const DEFAULT_CONFIG = {
8
77
  confidence_threshold: 0.5,
9
78
  custom_entities: [],
10
79
  entityActions: {},
80
+ entityConfidenceThresholds: {},
81
+ allowlist: {
82
+ values: [],
83
+ patterns: [],
84
+ entities: {},
85
+ },
86
+ auditEnabled: true,
11
87
  };
12
88
  export function loadConfig(overrides) {
13
- const config = { ...DEFAULT_CONFIG, ...overrides };
89
+ const config = {
90
+ ...DEFAULT_CONFIG,
91
+ ...overrides,
92
+ entityActions: {
93
+ ...DEFAULT_CONFIG.entityActions,
94
+ ...(overrides.entityActions ?? {}),
95
+ },
96
+ entityConfidenceThresholds: {
97
+ ...DEFAULT_CONFIG.entityConfidenceThresholds,
98
+ ...(overrides.entityConfidenceThresholds ?? {}),
99
+ },
100
+ };
101
+ config.allowlist = ensureEntityAllowlist(overrides.allowlist ?? DEFAULT_CONFIG.allowlist);
102
+ config.entityConfidenceThresholds = ensureEntityConfidenceThresholds(config.entityConfidenceThresholds);
103
+ if (typeof config.enabled !== "boolean") {
104
+ throw new Error(`enabled must be true or false`);
105
+ }
14
106
  if (!VALID_GUARDRAIL_MODES.includes(config.guardrail_mode)) {
15
107
  throw new Error(`Invalid guardrail_mode "${config.guardrail_mode}". Must be one of: ${VALID_GUARDRAIL_MODES.join(", ")}`);
16
108
  }
@@ -20,11 +112,18 @@ export function loadConfig(overrides) {
20
112
  if (config.confidence_threshold < 0 || config.confidence_threshold > 1) {
21
113
  throw new Error(`confidence_threshold must be between 0 and 1, got ${config.confidence_threshold}`);
22
114
  }
115
+ if (typeof config.auditEnabled !== "boolean") {
116
+ throw new Error(`auditEnabled must be true or false`);
117
+ }
118
+ const normalizedActions = {};
23
119
  for (const [entityType, action] of Object.entries(config.entityActions)) {
24
120
  if (!VALID_GUARDRAIL_MODES.includes(action)) {
25
121
  throw new Error(`Invalid action "${action}" for entity type "${entityType}". Must be one of: ${VALID_GUARDRAIL_MODES.join(", ")}`);
26
122
  }
123
+ const normalizedType = canonicalType(entityType);
124
+ normalizedActions[normalizedType] = action;
27
125
  }
126
+ config.entityActions = normalizedActions;
28
127
  return config;
29
128
  }
30
129
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,qBAAqB,GAAsB,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC7E,MAAM,uBAAuB,GAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5E,MAAM,CAAC,MAAM,cAAc,GAAkB;IAC3C,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,QAAQ;IACxB,cAAc,EAAE,OAAO;IACvB,KAAK,EAAE,kCAAkC;IACzC,oBAAoB,EAAE,GAAG;IACzB,eAAe,EAAE,EAAE;IACnB,aAAa,EAAE,EAAE;CAClB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,SAAiC;IAC1D,MAAM,MAAM,GAAkB,EAAE,GAAG,cAAc,EAAE,GAAG,SAAS,EAAE,CAAC;IAElE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,2BAA2B,MAAM,CAAC,cAAc,sBAAsB,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACzG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,2BAA2B,MAAM,CAAC,cAAc,sBAAsB,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3G,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,oBAAoB,GAAG,CAAC,IAAI,MAAM,CAAC,oBAAoB,GAAG,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CACb,qDAAqD,MAAM,CAAC,oBAAoB,EAAE,CACnF,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;QACxE,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CACb,mBAAmB,MAAM,sBAAsB,UAAU,sBAAsB,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Utilities for extracting text from AgentMessage tool result payloads
3
+ * and replacing text content after redaction.
4
+ *
5
+ * AgentMessage shapes handled:
6
+ * - Plain string
7
+ * - Object with `content: string`
8
+ * - Object with `content: [{ type: "text", text: "..." }, ...]`
9
+ *
10
+ * When multiple text blocks exist in a content array, they are joined
11
+ * with a null byte separator (\0) so entity offsets stay valid across
12
+ * the concatenated string. replaceText splits on the same separator
13
+ * to map redacted text back to individual blocks.
14
+ */
15
+ /**
16
+ * Extract all text content from an AgentMessage tool result payload.
17
+ * Returns an empty string if no text content is found.
18
+ */
19
+ export declare function extractText(message: unknown): string;
20
+ /**
21
+ * Replace text content in an AgentMessage tool result payload with
22
+ * the redacted version. Returns a shallow copy; does not mutate.
23
+ *
24
+ * If the message shape is not recognized or has no text, returns
25
+ * the original message unchanged.
26
+ */
27
+ export declare function replaceText(message: unknown, redactedText: string): unknown;
28
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CA4BpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAqC3E"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Utilities for extracting text from AgentMessage tool result payloads
3
+ * and replacing text content after redaction.
4
+ *
5
+ * AgentMessage shapes handled:
6
+ * - Plain string
7
+ * - Object with `content: string`
8
+ * - Object with `content: [{ type: "text", text: "..." }, ...]`
9
+ *
10
+ * When multiple text blocks exist in a content array, they are joined
11
+ * with a null byte separator (\0) so entity offsets stay valid across
12
+ * the concatenated string. replaceText splits on the same separator
13
+ * to map redacted text back to individual blocks.
14
+ */
15
+ // Separator between text segments from content block arrays.
16
+ // Null byte won't appear in regex PII patterns or normal text content.
17
+ const SEGMENT_SEP = "\0";
18
+ /**
19
+ * Extract all text content from an AgentMessage tool result payload.
20
+ * Returns an empty string if no text content is found.
21
+ */
22
+ export function extractText(message) {
23
+ if (message == null)
24
+ return "";
25
+ if (typeof message === "string")
26
+ return message;
27
+ if (typeof message !== "object")
28
+ return "";
29
+ const msg = message;
30
+ const content = msg.content;
31
+ if (content == null)
32
+ return "";
33
+ if (typeof content === "string")
34
+ return content;
35
+ if (Array.isArray(content)) {
36
+ const textParts = [];
37
+ for (const block of content) {
38
+ if (block != null &&
39
+ typeof block === "object" &&
40
+ block.type === "text" &&
41
+ typeof block.text === "string") {
42
+ textParts.push(block.text);
43
+ }
44
+ }
45
+ if (textParts.length === 0)
46
+ return "";
47
+ return textParts.join(SEGMENT_SEP);
48
+ }
49
+ return "";
50
+ }
51
+ /**
52
+ * Replace text content in an AgentMessage tool result payload with
53
+ * the redacted version. Returns a shallow copy; does not mutate.
54
+ *
55
+ * If the message shape is not recognized or has no text, returns
56
+ * the original message unchanged.
57
+ */
58
+ export function replaceText(message, redactedText) {
59
+ if (message == null)
60
+ return message;
61
+ if (typeof message === "string")
62
+ return redactedText;
63
+ if (typeof message !== "object")
64
+ return message;
65
+ const msg = message;
66
+ const content = msg.content;
67
+ if (content == null)
68
+ return message;
69
+ if (typeof content === "string") {
70
+ return { ...msg, content: redactedText };
71
+ }
72
+ if (Array.isArray(content)) {
73
+ const segments = redactedText.split(SEGMENT_SEP);
74
+ let segmentIndex = 0;
75
+ const newContent = content.map((block) => {
76
+ if (block != null &&
77
+ typeof block === "object" &&
78
+ block.type === "text" &&
79
+ typeof block.text === "string" &&
80
+ segmentIndex < segments.length) {
81
+ const replaced = { ...block, text: segments[segmentIndex] };
82
+ segmentIndex++;
83
+ return replaced;
84
+ }
85
+ return block;
86
+ });
87
+ return { ...msg, content: newContent };
88
+ }
89
+ return message;
90
+ }
91
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,6DAA6D;AAC7D,uEAAuE;AACvE,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC/B,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAE3C,MAAM,GAAG,GAAG,OAAkC,CAAC;IAC/C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAE5B,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC/B,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAEhD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IACE,KAAK,IAAI,IAAI;gBACb,OAAO,KAAK,KAAK,QAAQ;gBACxB,KAAiC,CAAC,IAAI,KAAK,MAAM;gBAClD,OAAQ,KAAiC,CAAC,IAAI,KAAK,QAAQ,EAC3D,CAAC;gBACD,SAAS,CAAC,IAAI,CAAE,KAAiC,CAAC,IAAc,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,OAAO,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,OAAgB,EAAE,YAAoB;IAChE,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,OAAO,CAAC;IACpC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC;IACrD,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAEhD,MAAM,GAAG,GAAG,OAAkC,CAAC;IAC/C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAE5B,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,OAAO,CAAC;IAEpC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACjD,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACvC,IACE,KAAK,IAAI,IAAI;gBACb,OAAO,KAAK,KAAK,QAAQ;gBACxB,KAAiC,CAAC,IAAI,KAAK,MAAM;gBAClD,OAAQ,KAAiC,CAAC,IAAI,KAAK,QAAQ;gBAC3D,YAAY,GAAG,QAAQ,CAAC,MAAM,EAC9B,CAAC;gBACD,MAAM,QAAQ,GAAG,EAAE,GAAI,KAAiC,EAAE,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBACzF,YAAY,EAAE,CAAC;gBACf,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QAEH,OAAO,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IACzC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export type { Entity, FogClawConfig, ScanResult, RedactResult, RedactStrategy, G
8
8
  * Registers:
9
9
  * - `before_agent_start` hook for automatic PII guardrail
10
10
  * - `fogclaw_scan` tool for on-demand entity detection
11
+ * - `fogclaw_preview` tool for dry-run policy simulation
11
12
  * - `fogclaw_redact` tool for on-demand redaction
12
13
  */
13
14
  declare const fogclaw: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EACV,MAAM,EACN,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,QAAA,MAAM,OAAO;;;kBAIG,GAAG;CA+LlB,CAAC;AAEF,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EACV,MAAM,EACN,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAqEpB;;;;;;;;GAQG;AACH,QAAA,MAAM,OAAO;;;kBAIG,GAAG;CA8QlB,CAAC;AAEF,eAAe,OAAO,CAAC"}