@datasynx/agentic-crm 0.1.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/LICENSE +21 -0
- package/README.md +767 -0
- package/dist/agent-config-zPvcqu07.js +14 -0
- package/dist/agent-config-zPvcqu07.js.map +1 -0
- package/dist/approvals-DpjxGHFp.js +67 -0
- package/dist/approvals-DpjxGHFp.js.map +1 -0
- package/dist/ask-CID3jnuL.js +52 -0
- package/dist/ask-CID3jnuL.js.map +1 -0
- package/dist/audit-log-DNMY9mUZ.js +49 -0
- package/dist/audit-log-DNMY9mUZ.js.map +1 -0
- package/dist/auth-CyFuu9X_.js +2 -0
- package/dist/auth-DFWwWcYD.js +93 -0
- package/dist/auth-DFWwWcYD.js.map +1 -0
- package/dist/autofill-Di_-SP7t.js +51 -0
- package/dist/autofill-Di_-SP7t.js.map +1 -0
- package/dist/backup-CeMk9z86.js +417 -0
- package/dist/backup-CeMk9z86.js.map +1 -0
- package/dist/backup-f_hC7rBV.js +2 -0
- package/dist/calendly-Bft_wwji.js +52 -0
- package/dist/calendly-Bft_wwji.js.map +1 -0
- package/dist/calendly-D3coO92o.cjs +53 -0
- package/dist/calendly-D3coO92o.cjs.map +1 -0
- package/dist/chunk-DakpK96I.cjs +43 -0
- package/dist/churn-C28IgnAj.js +54 -0
- package/dist/churn-C28IgnAj.js.map +1 -0
- package/dist/cli.js +4396 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors-BG07TZQz.js +11 -0
- package/dist/colors-BG07TZQz.js.map +1 -0
- package/dist/compliance-B1kk5-YS.js +115 -0
- package/dist/compliance-B1kk5-YS.js.map +1 -0
- package/dist/compliance-B91zNvCR.cjs +156 -0
- package/dist/compliance-B91zNvCR.cjs.map +1 -0
- package/dist/compliance-CKSBoQUe.js +118 -0
- package/dist/compliance-CKSBoQUe.js.map +1 -0
- package/dist/compliance-CujOqAKk.js +2 -0
- package/dist/context-builder-BzWAp3Zs.js +96 -0
- package/dist/context-builder-BzWAp3Zs.js.map +1 -0
- package/dist/context-builder-DlrRcqmJ.js +2 -0
- package/dist/conversation-intel-mm7Lhemh.js +72 -0
- package/dist/conversation-intel-mm7Lhemh.js.map +1 -0
- package/dist/custom-fields-CzNeD3_v.js +2 -0
- package/dist/custom-fields-Pl2t9xzp.js +73 -0
- package/dist/custom-fields-Pl2t9xzp.js.map +1 -0
- package/dist/custom-objects-BHgn1GEX.js +78 -0
- package/dist/custom-objects-BHgn1GEX.js.map +1 -0
- package/dist/custom-objects-CIFrmQ2V.js +2 -0
- package/dist/customer-dir-DIylZ8Q6.js +75 -0
- package/dist/customer-dir-DIylZ8Q6.js.map +1 -0
- package/dist/daemon/worker.js +207 -0
- package/dist/daemon/worker.js.map +1 -0
- package/dist/enrichment-3XvgGDfB.js +103 -0
- package/dist/enrichment-3XvgGDfB.js.map +1 -0
- package/dist/file-lock-B_zi7NQl.js +22 -0
- package/dist/file-lock-B_zi7NQl.js.map +1 -0
- package/dist/gmail-auth-BP6cJwfw.js +40 -0
- package/dist/gmail-auth-BP6cJwfw.js.map +1 -0
- package/dist/gmail-auth-DxakCtGm.cjs +44 -0
- package/dist/gmail-auth-DxakCtGm.cjs.map +1 -0
- package/dist/gmail-auth-OComS92L.js +40 -0
- package/dist/gmail-auth-OComS92L.js.map +1 -0
- package/dist/gmail-push-watch-DELQFMPk.js +20 -0
- package/dist/gmail-push-watch-DELQFMPk.js.map +1 -0
- package/dist/gmail-sender-StTpJ9Ub.js +32 -0
- package/dist/gmail-sender-StTpJ9Ub.js.map +1 -0
- package/dist/gmail-sync-DIaxInDT.js +204 -0
- package/dist/gmail-sync-DIaxInDT.js.map +1 -0
- package/dist/gmail-sync-hHm9gaWd.cjs +218 -0
- package/dist/gmail-sync-hHm9gaWd.cjs.map +1 -0
- package/dist/gmail-sync-rQaVqKWd.js +214 -0
- package/dist/gmail-sync-rQaVqKWd.js.map +1 -0
- package/dist/gmail-webhook-handler-DS7OlRPX.js +3 -0
- package/dist/gmail-webhook-handler-e5Od25FX.js +97 -0
- package/dist/gmail-webhook-handler-e5Od25FX.js.map +1 -0
- package/dist/goal-engine-CUZSpERI.js +2 -0
- package/dist/goal-engine-KpBftn4V.js +295 -0
- package/dist/goal-engine-KpBftn4V.js.map +1 -0
- package/dist/google-drive-sync-DEPcqFca.js +105 -0
- package/dist/google-drive-sync-DEPcqFca.js.map +1 -0
- package/dist/hybrid-search-BmHttLrR.js +40 -0
- package/dist/hybrid-search-BmHttLrR.js.map +1 -0
- package/dist/hygiene-DZqfYpFf.js +38 -0
- package/dist/hygiene-DZqfYpFf.js.map +1 -0
- package/dist/identity-CI6olMNm.js +41 -0
- package/dist/identity-CI6olMNm.js.map +1 -0
- package/dist/identity-gyfWdrcX.js +2 -0
- package/dist/import-hubspot-BaK71U_K.js +588 -0
- package/dist/import-hubspot-BaK71U_K.js.map +1 -0
- package/dist/index-V8BFaH-b.d.ts +539 -0
- package/dist/index-V8BFaH-b.d.ts.map +1 -0
- package/dist/index-YqwMd6aQ.d.cts +538 -0
- package/dist/index-YqwMd6aQ.d.cts.map +1 -0
- package/dist/index.cjs +185 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +538 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +539 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions-writer-CrPStUll.cjs +77 -0
- package/dist/interactions-writer-CrPStUll.cjs.map +1 -0
- package/dist/interactions-writer-DO3KcSR3.js +52 -0
- package/dist/interactions-writer-DO3KcSR3.js.map +1 -0
- package/dist/interactions-writer-SLHnoEeE.js +46 -0
- package/dist/interactions-writer-SLHnoEeE.js.map +1 -0
- package/dist/interactions-writer-dSPy1XfO.js +2 -0
- package/dist/knowledge-base-D0Fh40kc.js +1013 -0
- package/dist/knowledge-base-D0Fh40kc.js.map +1 -0
- package/dist/lancedb-CCBbpulq.js +2 -0
- package/dist/lancedb-rlvWoPwl.js +98 -0
- package/dist/lancedb-rlvWoPwl.js.map +1 -0
- package/dist/lead-model-BCFzyktm.js +109 -0
- package/dist/lead-model-BCFzyktm.js.map +1 -0
- package/dist/llm-DEjWcqmW.js +2 -0
- package/dist/llm-DvzZqva0.js +372 -0
- package/dist/llm-DvzZqva0.js.map +1 -0
- package/dist/llm-Z8RIYkpF.js +174 -0
- package/dist/llm-Z8RIYkpF.js.map +1 -0
- package/dist/llm-iijeXmgq.cjs +198 -0
- package/dist/llm-iijeXmgq.cjs.map +1 -0
- package/dist/mcp-CdTJWTJf.d.cts +12 -0
- package/dist/mcp-CdTJWTJf.d.cts.map +1 -0
- package/dist/mcp-CdTJWTJf.d.ts +12 -0
- package/dist/mcp-CdTJWTJf.d.ts.map +1 -0
- package/dist/mcp.cjs +7464 -0
- package/dist/mcp.cjs.map +1 -0
- package/dist/mcp.d.cts +12 -0
- package/dist/mcp.d.cts.map +1 -0
- package/dist/mcp.d.ts +12 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +7448 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-Bb6ky3kb.js +58 -0
- package/dist/memory-Bb6ky3kb.js.map +1 -0
- package/dist/memory-Cy6-Tbyl.js +2 -0
- package/dist/metrics-DH8wHvya.js +26 -0
- package/dist/metrics-DH8wHvya.js.map +1 -0
- package/dist/microsoft-auth-B8_S45gh.js +17 -0
- package/dist/microsoft-auth-B8_S45gh.js.map +1 -0
- package/dist/microsoft-calendar-B6MMtUQK.js +67 -0
- package/dist/microsoft-calendar-B6MMtUQK.js.map +1 -0
- package/dist/microsoft-sync-CpZVoSuq.js +68 -0
- package/dist/microsoft-sync-CpZVoSuq.js.map +1 -0
- package/dist/nba-3wanmJ0U.js +48 -0
- package/dist/nba-3wanmJ0U.js.map +1 -0
- package/dist/notification-dispatcher-0vYNngWe.js +97 -0
- package/dist/notification-dispatcher-0vYNngWe.js.map +1 -0
- package/dist/opportunity-score-BTMOQSTV.js +47 -0
- package/dist/opportunity-score-BTMOQSTV.js.map +1 -0
- package/dist/pipedrive-client-CdGKpH9b.js +17 -0
- package/dist/pipedrive-client-CdGKpH9b.js.map +1 -0
- package/dist/pipeline-writer-BqBrYrQc.js +2 -0
- package/dist/pipeline-writer-BvVquKIe.js +96 -0
- package/dist/pipeline-writer-BvVquKIe.js.map +1 -0
- package/dist/pipeline-writer-N2omexxp.cjs +121 -0
- package/dist/pipeline-writer-N2omexxp.cjs.map +1 -0
- package/dist/pipeline-writer-eufx_0o1.js +102 -0
- package/dist/pipeline-writer-eufx_0o1.js.map +1 -0
- package/dist/proactive-agent-BgQXw3ac.js +96 -0
- package/dist/proactive-agent-BgQXw3ac.js.map +1 -0
- package/dist/proactive-worker-BrLHNhjH.js +229 -0
- package/dist/proactive-worker-BrLHNhjH.js.map +1 -0
- package/dist/push-manager-CdqIIkuh.js +108 -0
- package/dist/push-manager-CdqIIkuh.js.map +1 -0
- package/dist/push-manager-CowY-0IK.js +2 -0
- package/dist/quote-generator-BfwENXzg.js +133 -0
- package/dist/quote-generator-BfwENXzg.js.map +1 -0
- package/dist/quote-generator-OhSFsi3x.js +2 -0
- package/dist/rbac-C7c8tcES.js +2 -0
- package/dist/rbac-CTIktZaC.js +91 -0
- package/dist/rbac-CTIktZaC.js.map +1 -0
- package/dist/relationship-health-odxEoQdJ.js +454 -0
- package/dist/relationship-health-odxEoQdJ.js.map +1 -0
- package/dist/revenue-simulation-BJdRTEHc.js +2 -0
- package/dist/revenue-simulation-Bqf2DLVB.js +251 -0
- package/dist/revenue-simulation-Bqf2DLVB.js.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/salesforce-client-rhZFa_p5.js +51 -0
- package/dist/salesforce-client-rhZFa_p5.js.map +1 -0
- package/dist/segments-BqcD5HIl.js +61 -0
- package/dist/segments-BqcD5HIl.js.map +1 -0
- package/dist/sequence-engine-CCTHEBgi.js +2 -0
- package/dist/sequence-engine-J1lTW_in.js +91 -0
- package/dist/sequence-engine-J1lTW_in.js.map +1 -0
- package/dist/sequence-store-DaaWr0Os.js +221 -0
- package/dist/sequence-store-DaaWr0Os.js.map +1 -0
- package/dist/server-Dyva03K8.js +4287 -0
- package/dist/server-Dyva03K8.js.map +1 -0
- package/dist/session-B9AilxOE.js +81 -0
- package/dist/session-B9AilxOE.js.map +1 -0
- package/dist/session-D0qFkBla.cjs +82 -0
- package/dist/session-D0qFkBla.cjs.map +1 -0
- package/dist/session-D9ub6Wl1.js +79 -0
- package/dist/session-D9ub6Wl1.js.map +1 -0
- package/dist/session-mWHA71Lw.js +2 -0
- package/dist/session-store-B0QZE8Bx.cjs +697 -0
- package/dist/session-store-B0QZE8Bx.cjs.map +1 -0
- package/dist/session-store-C8tEvMPw.js +543 -0
- package/dist/session-store-C8tEvMPw.js.map +1 -0
- package/dist/session-store-CEa39Dxs.js +15 -0
- package/dist/session-store-CEa39Dxs.js.map +1 -0
- package/dist/sla-engine-5IhTsBUR.js +2 -0
- package/dist/sla-engine-BqX-7u-7.js +53 -0
- package/dist/sla-engine-BqX-7u-7.js.map +1 -0
- package/dist/sop-DkhVChGy.js +2 -0
- package/dist/sop-Vp0UPWFW.js +70 -0
- package/dist/sop-Vp0UPWFW.js.map +1 -0
- package/dist/survey-engine-C06hcQt3.js +2 -0
- package/dist/survey-engine-DBjCYqCv.js +147 -0
- package/dist/survey-engine-DBjCYqCv.js.map +1 -0
- package/dist/sync-state-ChaLbamC.js +33 -0
- package/dist/sync-state-ChaLbamC.js.map +1 -0
- package/dist/sync-state-CwLSt_1m.js +2 -0
- package/dist/ticket-writer-CjqKeIRD.js +2 -0
- package/dist/ticket-writer-j2oX_Wal.js +134 -0
- package/dist/ticket-writer-j2oX_Wal.js.map +1 -0
- package/dist/tone-Bdm5uaht.js +48 -0
- package/dist/tone-Bdm5uaht.js.map +1 -0
- package/dist/tone-DRKlZgPr.cjs +43 -0
- package/dist/tone-DRKlZgPr.cjs.map +1 -0
- package/dist/tone-vNb2DAAD.js +39 -0
- package/dist/tone-vNb2DAAD.js.map +1 -0
- package/dist/transcript-watcher-CL2QUygI.js +132 -0
- package/dist/transcript-watcher-CL2QUygI.js.map +1 -0
- package/dist/unmatched-transcripts-BsH5bhkU.js +26 -0
- package/dist/unmatched-transcripts-BsH5bhkU.js.map +1 -0
- package/dist/unmatched-transcripts-D0PrJ9iz.js +2 -0
- package/dist/update-deal-BNwPGaTV.js +2 -0
- package/dist/update-deal-DKC79skb.js +91 -0
- package/dist/update-deal-DKC79skb.js.map +1 -0
- package/dist/usage-CClTf5e6.cjs +57 -0
- package/dist/usage-CClTf5e6.cjs.map +1 -0
- package/dist/usage-D0-TYJkw.js +93 -0
- package/dist/usage-D0-TYJkw.js.map +1 -0
- package/dist/usage-D0u9a-lV.js +54 -0
- package/dist/usage-D0u9a-lV.js.map +1 -0
- package/dist/vault-C1D3zScD.js +2 -0
- package/dist/vault-DXCg29W-.js +86 -0
- package/dist/vault-DXCg29W-.js.map +1 -0
- package/dist/webhooks-7EpA05Qr.js +138 -0
- package/dist/webhooks-7EpA05Qr.js.map +1 -0
- package/dist/webhooks-BO2UAnmn.js +94 -0
- package/dist/webhooks-BO2UAnmn.js.map +1 -0
- package/dist/webhooks-Xn6zO6kd.cjs +97 -0
- package/dist/webhooks-Xn6zO6kd.cjs.map +1 -0
- package/dist/write-queue-BDolUxfs.cjs +26 -0
- package/dist/write-queue-BDolUxfs.cjs.map +1 -0
- package/dist/write-queue-IbsAjUnh.js +21 -0
- package/dist/write-queue-IbsAjUnh.js.map +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ansis from "ansis";
|
|
2
|
+
//#region src/ui/colors.ts
|
|
3
|
+
const success = (s) => ansis.green(s);
|
|
4
|
+
const error = (s) => ansis.red(s);
|
|
5
|
+
const warning = (s) => ansis.yellow(s);
|
|
6
|
+
const info = (s) => ansis.cyan(s);
|
|
7
|
+
const bold = (s) => ansis.bold(s);
|
|
8
|
+
//#endregion
|
|
9
|
+
export { warning as a, success as i, error as n, info as r, bold as t };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=colors-BG07TZQz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors-BG07TZQz.js","names":[],"sources":["../src/ui/colors.ts"],"sourcesContent":["import ansis from \"ansis\";\n\nexport const success = (s: string): string => ansis.green(s);\nexport const error = (s: string): string => ansis.red(s);\nexport const warning = (s: string): string => ansis.yellow(s);\nexport const info = (s: string): string => ansis.cyan(s);\nexport const muted = (s: string): string => ansis.gray(s);\nexport const bold = (s: string): string => ansis.bold(s);\n"],"mappings":";;AAEA,MAAa,WAAW,MAAsB,MAAM,MAAM,CAAC;AAC3D,MAAa,SAAS,MAAsB,MAAM,IAAI,CAAC;AACvD,MAAa,WAAW,MAAsB,MAAM,OAAO,CAAC;AAC5D,MAAa,QAAQ,MAAsB,MAAM,KAAK,CAAC;AAEvD,MAAa,QAAQ,MAAsB,MAAM,KAAK,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
//#region src/core/pii.ts
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight PII masking for LLM calls (Einstein-Trust-Layer-style).
|
|
5
|
+
* Replaces emails and phone numbers with stable placeholders before text is
|
|
6
|
+
* sent to an LLM, and can restore them in the response. Opt-in via
|
|
7
|
+
* DXCRM_PII_MASKING=on so default behaviour and quality are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
const EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
10
|
+
const PHONE_RE = /\+?\d[\d\s().-]{6,}\d/g;
|
|
11
|
+
function piiMaskingEnabled() {
|
|
12
|
+
return process.env["DXCRM_PII_MASKING"] === "on";
|
|
13
|
+
}
|
|
14
|
+
function maskPii(input) {
|
|
15
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
16
|
+
let emailCount = 0;
|
|
17
|
+
let phoneCount = 0;
|
|
18
|
+
let masked = input.replace(EMAIL_RE, (match) => {
|
|
19
|
+
let ph = mapping.get(match);
|
|
20
|
+
if (!ph) {
|
|
21
|
+
ph = `[EMAIL_${emailCount++}]`;
|
|
22
|
+
mapping.set(match, ph);
|
|
23
|
+
}
|
|
24
|
+
return ph;
|
|
25
|
+
});
|
|
26
|
+
masked = masked.replace(PHONE_RE, (match) => {
|
|
27
|
+
let ph = mapping.get(match);
|
|
28
|
+
if (!ph) {
|
|
29
|
+
ph = `[PHONE_${phoneCount++}]`;
|
|
30
|
+
mapping.set(match, ph);
|
|
31
|
+
}
|
|
32
|
+
return ph;
|
|
33
|
+
});
|
|
34
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const [orig, ph] of mapping) reverse.set(ph, orig);
|
|
36
|
+
const unmask = (text) => {
|
|
37
|
+
let out = text;
|
|
38
|
+
for (const [ph, orig] of reverse) out = out.split(ph).join(orig);
|
|
39
|
+
return out;
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
masked,
|
|
43
|
+
unmask
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/core/guardrails.ts
|
|
48
|
+
/**
|
|
49
|
+
* Guardrails against (indirect) prompt injection in untrusted CRM content
|
|
50
|
+
* (emails, transcripts, imported notes). Detection is always available;
|
|
51
|
+
* neutralization is applied to LLM inputs when DXCRM_GUARDRAILS=on.
|
|
52
|
+
*/
|
|
53
|
+
const INJECTION_PATTERNS = [
|
|
54
|
+
/ignore\s+(all\s+|the\s+|any\s+)?(previous|above|prior|preceding)\s+(instructions?|prompts?|messages?)/gi,
|
|
55
|
+
/disregard\s+(all\s+|the\s+|any\s+)?(previous|above|prior|preceding)/gi,
|
|
56
|
+
/forget\s+(all\s+|the\s+|everything\s+)?(previous|above|prior|you were told)/gi,
|
|
57
|
+
/you\s+are\s+now\b/gi,
|
|
58
|
+
/reveal\s+(your|the)\s+(system\s+)?(instructions?|prompt|prompts?)/gi,
|
|
59
|
+
/(print|show|repeat)\s+(your|the)\s+(system\s+)?(instructions?|prompt)/gi,
|
|
60
|
+
/act\s+as\s+(an?\s+)?(system|admin|administrator|developer|root)\b/gi,
|
|
61
|
+
/<\/?(system|assistant|tool)\b[^>]*>/gi,
|
|
62
|
+
/\bBEGIN\s+SYSTEM\b/gi
|
|
63
|
+
];
|
|
64
|
+
function guardrailsEnabled() {
|
|
65
|
+
return process.env["DXCRM_GUARDRAILS"] === "on";
|
|
66
|
+
}
|
|
67
|
+
/** Replace known injection spans with a [filtered] marker, keeping other text. */
|
|
68
|
+
function neutralizeUntrusted(text) {
|
|
69
|
+
let out = text;
|
|
70
|
+
for (const re of INJECTION_PATTERNS) out = out.replace(re, "[filtered]");
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/core/compliance.ts
|
|
75
|
+
var compliance_exports = /* @__PURE__ */ __exportAll({
|
|
76
|
+
aiDisclosure: () => aiDisclosure,
|
|
77
|
+
aiDisclosureEnabled: () => aiDisclosureEnabled,
|
|
78
|
+
labelAiContent: () => labelAiContent,
|
|
79
|
+
llmProvider: () => llmProvider,
|
|
80
|
+
localLlmConfig: () => localLlmConfig
|
|
81
|
+
});
|
|
82
|
+
const DISCLOSURES = {
|
|
83
|
+
de: "Hinweis: Dieser Inhalt wurde mithilfe von KI erstellt (EU-AI-Act Art. 50).",
|
|
84
|
+
en: "Note: This content was generated with the help of AI (EU AI Act Art. 50)."
|
|
85
|
+
};
|
|
86
|
+
/** Localized Art. 50 disclosure string. */
|
|
87
|
+
function aiDisclosure(lang = "de") {
|
|
88
|
+
return DISCLOSURES[lang] ?? DISCLOSURES.en;
|
|
89
|
+
}
|
|
90
|
+
/** Disclosure is ON by default (Art. 50); opt out with DXCRM_AI_DISCLOSURE=off. */
|
|
91
|
+
function aiDisclosureEnabled() {
|
|
92
|
+
return process.env["DXCRM_AI_DISCLOSURE"] !== "off";
|
|
93
|
+
}
|
|
94
|
+
/** Prepend the Art. 50 disclosure to generated content (unless disabled). */
|
|
95
|
+
function labelAiContent(text, opts = {}) {
|
|
96
|
+
if (!(opts.enabled ?? aiDisclosureEnabled())) return text;
|
|
97
|
+
return `${aiDisclosure(opts.lang ?? (process.env["DXCRM_AI_DISCLOSURE_LANG"] || "de"))}\n\n${text}`;
|
|
98
|
+
}
|
|
99
|
+
/** Resolve the configured LLM provider (default Anthropic). */
|
|
100
|
+
function llmProvider() {
|
|
101
|
+
const p = (process.env["DXCRM_LLM_PROVIDER"] ?? "anthropic").toLowerCase();
|
|
102
|
+
if (p === "ollama" || p === "openai" || p === "local") return p;
|
|
103
|
+
return "anthropic";
|
|
104
|
+
}
|
|
105
|
+
/** OpenAI-compatible local endpoint config (Ollama defaults), env-overridable. */
|
|
106
|
+
function localLlmConfig() {
|
|
107
|
+
return {
|
|
108
|
+
baseUrl: process.env["DXCRM_LLM_BASE_URL"] ?? "http://127.0.0.1:11434/v1",
|
|
109
|
+
model: process.env["DXCRM_LLM_MODEL"] ?? "llama3.1"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { neutralizeUntrusted as a, guardrailsEnabled as i, llmProvider as n, maskPii as o, localLlmConfig as r, piiMaskingEnabled as s, compliance_exports as t };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=compliance-B1kk5-YS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-B1kk5-YS.js","names":[],"sources":["../src/core/pii.ts","../src/core/guardrails.ts","../src/core/compliance.ts"],"sourcesContent":["/**\n * Lightweight PII masking for LLM calls (Einstein-Trust-Layer-style).\n * Replaces emails and phone numbers with stable placeholders before text is\n * sent to an LLM, and can restore them in the response. Opt-in via\n * DXCRM_PII_MASKING=on so default behaviour and quality are unchanged.\n */\n\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\n// Phone: optional +, then 7+ digits possibly grouped by spaces/dashes/parens.\nconst PHONE_RE = /\\+?\\d[\\d\\s().-]{6,}\\d/g;\n\nexport interface MaskedText {\n masked: string;\n unmask: (text: string) => string;\n}\n\nexport function piiMaskingEnabled(): boolean {\n return process.env[\"DXCRM_PII_MASKING\"] === \"on\";\n}\n\nexport function maskPii(input: string): MaskedText {\n const mapping = new Map<string, string>(); // original -> placeholder\n let emailCount = 0;\n let phoneCount = 0;\n\n let masked = input.replace(EMAIL_RE, (match) => {\n let ph = mapping.get(match);\n if (!ph) {\n ph = `[EMAIL_${emailCount++}]`;\n mapping.set(match, ph);\n }\n return ph;\n });\n\n masked = masked.replace(PHONE_RE, (match) => {\n // Ignore very short numeric runs that slipped through (already handled by {6,}).\n let ph = mapping.get(match);\n if (!ph) {\n ph = `[PHONE_${phoneCount++}]`;\n mapping.set(match, ph);\n }\n return ph;\n });\n\n const reverse = new Map<string, string>(); // placeholder -> original\n for (const [orig, ph] of mapping) reverse.set(ph, orig);\n\n const unmask = (text: string): string => {\n let out = text;\n for (const [ph, orig] of reverse) out = out.split(ph).join(orig);\n return out;\n };\n\n return { masked, unmask };\n}\n","/**\n * Guardrails against (indirect) prompt injection in untrusted CRM content\n * (emails, transcripts, imported notes). Detection is always available;\n * neutralization is applied to LLM inputs when DXCRM_GUARDRAILS=on.\n */\n\nconst INJECTION_PATTERNS: RegExp[] = [\n /ignore\\s+(all\\s+|the\\s+|any\\s+)?(previous|above|prior|preceding)\\s+(instructions?|prompts?|messages?)/gi,\n /disregard\\s+(all\\s+|the\\s+|any\\s+)?(previous|above|prior|preceding)/gi,\n /forget\\s+(all\\s+|the\\s+|everything\\s+)?(previous|above|prior|you were told)/gi,\n /you\\s+are\\s+now\\b/gi,\n /reveal\\s+(your|the)\\s+(system\\s+)?(instructions?|prompt|prompts?)/gi,\n /(print|show|repeat)\\s+(your|the)\\s+(system\\s+)?(instructions?|prompt)/gi,\n /act\\s+as\\s+(an?\\s+)?(system|admin|administrator|developer|root)\\b/gi,\n /<\\/?(system|assistant|tool)\\b[^>]*>/gi,\n /\\bBEGIN\\s+SYSTEM\\b/gi,\n];\n\nexport function guardrailsEnabled(): boolean {\n return process.env[\"DXCRM_GUARDRAILS\"] === \"on\";\n}\n\nexport function detectPromptInjection(text: string): { flagged: boolean; matches: string[] } {\n const matches: string[] = [];\n for (const re of INJECTION_PATTERNS) {\n re.lastIndex = 0;\n const found = text.match(re);\n if (found) matches.push(...found);\n }\n return { flagged: matches.length > 0, matches };\n}\n\n/** Replace known injection spans with a [filtered] marker, keeping other text. */\nexport function neutralizeUntrusted(text: string): string {\n let out = text;\n for (const re of INJECTION_PATTERNS) {\n out = out.replace(re, \"[filtered]\");\n }\n return out;\n}\n","import { piiMaskingEnabled } from \"./pii.js\";\nimport { guardrailsEnabled } from \"./guardrails.js\";\n\n/**\n * Compliance hardening (domino D17 / §3): the cross-cutting governance layer.\n * - EU-AI-Act Art. 50 transparency: a localized disclosure that AI generated\n * the content, on by default (opt-out), wrappable around any generated text.\n * - Provider-agnostic LLM selection so the workspace can run against a local,\n * self-hosted model (Ollama / OpenAI-compatible) as a data-residency moat —\n * no customer data leaves the machine. The provider runtime itself stays in\n * the agent framework; here we only resolve and expose the configuration.\n * - A single read-out of the active privacy/compliance posture.\n */\nexport type DisclosureLang = \"de\" | \"en\";\n\nexport type LlmProviderName = \"anthropic\" | \"ollama\" | \"openai\" | \"local\";\n\nconst DISCLOSURES: Record<DisclosureLang, string> = {\n de: \"Hinweis: Dieser Inhalt wurde mithilfe von KI erstellt (EU-AI-Act Art. 50).\",\n en: \"Note: This content was generated with the help of AI (EU AI Act Art. 50).\",\n};\n\n/** Localized Art. 50 disclosure string. */\nexport function aiDisclosure(lang: DisclosureLang = \"de\"): string {\n return DISCLOSURES[lang] ?? DISCLOSURES.en;\n}\n\n/** Disclosure is ON by default (Art. 50); opt out with DXCRM_AI_DISCLOSURE=off. */\nexport function aiDisclosureEnabled(): boolean {\n return process.env[\"DXCRM_AI_DISCLOSURE\"] !== \"off\";\n}\n\n/** Prepend the Art. 50 disclosure to generated content (unless disabled). */\nexport function labelAiContent(\n text: string,\n opts: { lang?: DisclosureLang; enabled?: boolean } = {}\n): string {\n const enabled = opts.enabled ?? aiDisclosureEnabled();\n if (!enabled) return text;\n const lang = opts.lang ?? ((process.env[\"DXCRM_AI_DISCLOSURE_LANG\"] as DisclosureLang) || \"de\");\n return `${aiDisclosure(lang)}\\n\\n${text}`;\n}\n\n/** Resolve the configured LLM provider (default Anthropic). */\nexport function llmProvider(): LlmProviderName {\n const p = (process.env[\"DXCRM_LLM_PROVIDER\"] ?? \"anthropic\").toLowerCase();\n if (p === \"ollama\" || p === \"openai\" || p === \"local\") return p;\n return \"anthropic\";\n}\n\nexport interface LocalLlmConfig {\n baseUrl: string;\n model: string;\n}\n\n/** OpenAI-compatible local endpoint config (Ollama defaults), env-overridable. */\nexport function localLlmConfig(): LocalLlmConfig {\n return {\n baseUrl: process.env[\"DXCRM_LLM_BASE_URL\"] ?? \"http://127.0.0.1:11434/v1\",\n model: process.env[\"DXCRM_LLM_MODEL\"] ?? \"llama3.1\",\n };\n}\n\nexport interface ComplianceConfig {\n provider: LlmProviderName;\n local: LocalLlmConfig | null;\n aiDisclosure: boolean;\n piiMasking: boolean;\n guardrails: boolean;\n}\n\n/** A single read-out of the active privacy/compliance posture. */\nexport function complianceConfig(): ComplianceConfig {\n const provider = llmProvider();\n return {\n provider,\n local: provider === \"anthropic\" ? null : localLlmConfig(),\n aiDisclosure: aiDisclosureEnabled(),\n piiMasking: piiMaskingEnabled(),\n guardrails: guardrailsEnabled(),\n };\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,WAAW;AAEjB,MAAM,WAAW;AAOjB,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,IAAI,yBAAyB;AAC9C;AAEA,SAAgB,QAAQ,OAA2B;CACjD,MAAM,0BAAU,IAAI,IAAoB;CACxC,IAAI,aAAa;CACjB,IAAI,aAAa;CAEjB,IAAI,SAAS,MAAM,QAAQ,WAAW,UAAU;EAC9C,IAAI,KAAK,QAAQ,IAAI,KAAK;EAC1B,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,aAAa;GAC5B,QAAQ,IAAI,OAAO,EAAE;EACvB;EACA,OAAO;CACT,CAAC;CAED,SAAS,OAAO,QAAQ,WAAW,UAAU;EAE3C,IAAI,KAAK,QAAQ,IAAI,KAAK;EAC1B,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,aAAa;GAC5B,QAAQ,IAAI,OAAO,EAAE;EACvB;EACA,OAAO;CACT,CAAC;CAED,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,CAAC,MAAM,OAAO,SAAS,QAAQ,IAAI,IAAI,IAAI;CAEtD,MAAM,UAAU,SAAyB;EACvC,IAAI,MAAM;EACV,KAAK,MAAM,CAAC,IAAI,SAAS,SAAS,MAAM,IAAI,MAAM,EAAE,EAAE,KAAK,IAAI;EAC/D,OAAO;CACT;CAEA,OAAO;EAAE;EAAQ;CAAO;AAC1B;;;;;;;;AChDA,MAAM,qBAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,IAAI,wBAAwB;AAC7C;;AAaA,SAAgB,oBAAoB,MAAsB;CACxD,IAAI,MAAM;CACV,KAAK,MAAM,MAAM,oBACf,MAAM,IAAI,QAAQ,IAAI,YAAY;CAEpC,OAAO;AACT;;;;;;;;;;ACtBA,MAAM,cAA8C;CAClD,IAAI;CACJ,IAAI;AACN;;AAGA,SAAgB,aAAa,OAAuB,MAAc;CAChE,OAAO,YAAY,SAAS,YAAY;AAC1C;;AAGA,SAAgB,sBAA+B;CAC7C,OAAO,QAAQ,IAAI,2BAA2B;AAChD;;AAGA,SAAgB,eACd,MACA,OAAqD,CAAC,GAC9C;CAER,IAAI,EADY,KAAK,WAAW,oBAAoB,IACtC,OAAO;CAErB,OAAO,GAAG,aADG,KAAK,SAAU,QAAQ,IAAI,+BAAkD,KAC/D,EAAE,MAAM;AACrC;;AAGA,SAAgB,cAA+B;CAC7C,MAAM,KAAK,QAAQ,IAAI,yBAAyB,aAAa,YAAY;CACzE,IAAI,MAAM,YAAY,MAAM,YAAY,MAAM,SAAS,OAAO;CAC9D,OAAO;AACT;;AAQA,SAAgB,iBAAiC;CAC/C,OAAO;EACL,SAAS,QAAQ,IAAI,yBAAyB;EAC9C,OAAO,QAAQ,IAAI,sBAAsB;CAC3C;AACF"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-DakpK96I.cjs");
|
|
2
|
+
//#region src/core/pii.ts
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight PII masking for LLM calls (Einstein-Trust-Layer-style).
|
|
5
|
+
* Replaces emails and phone numbers with stable placeholders before text is
|
|
6
|
+
* sent to an LLM, and can restore them in the response. Opt-in via
|
|
7
|
+
* DXCRM_PII_MASKING=on so default behaviour and quality are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
const EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
10
|
+
const PHONE_RE = /\+?\d[\d\s().-]{6,}\d/g;
|
|
11
|
+
function piiMaskingEnabled() {
|
|
12
|
+
return process.env["DXCRM_PII_MASKING"] === "on";
|
|
13
|
+
}
|
|
14
|
+
function maskPii(input) {
|
|
15
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
16
|
+
let emailCount = 0;
|
|
17
|
+
let phoneCount = 0;
|
|
18
|
+
let masked = input.replace(EMAIL_RE, (match) => {
|
|
19
|
+
let ph = mapping.get(match);
|
|
20
|
+
if (!ph) {
|
|
21
|
+
ph = `[EMAIL_${emailCount++}]`;
|
|
22
|
+
mapping.set(match, ph);
|
|
23
|
+
}
|
|
24
|
+
return ph;
|
|
25
|
+
});
|
|
26
|
+
masked = masked.replace(PHONE_RE, (match) => {
|
|
27
|
+
let ph = mapping.get(match);
|
|
28
|
+
if (!ph) {
|
|
29
|
+
ph = `[PHONE_${phoneCount++}]`;
|
|
30
|
+
mapping.set(match, ph);
|
|
31
|
+
}
|
|
32
|
+
return ph;
|
|
33
|
+
});
|
|
34
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const [orig, ph] of mapping) reverse.set(ph, orig);
|
|
36
|
+
const unmask = (text) => {
|
|
37
|
+
let out = text;
|
|
38
|
+
for (const [ph, orig] of reverse) out = out.split(ph).join(orig);
|
|
39
|
+
return out;
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
masked,
|
|
43
|
+
unmask
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/core/guardrails.ts
|
|
48
|
+
/**
|
|
49
|
+
* Guardrails against (indirect) prompt injection in untrusted CRM content
|
|
50
|
+
* (emails, transcripts, imported notes). Detection is always available;
|
|
51
|
+
* neutralization is applied to LLM inputs when DXCRM_GUARDRAILS=on.
|
|
52
|
+
*/
|
|
53
|
+
const INJECTION_PATTERNS = [
|
|
54
|
+
/ignore\s+(all\s+|the\s+|any\s+)?(previous|above|prior|preceding)\s+(instructions?|prompts?|messages?)/gi,
|
|
55
|
+
/disregard\s+(all\s+|the\s+|any\s+)?(previous|above|prior|preceding)/gi,
|
|
56
|
+
/forget\s+(all\s+|the\s+|everything\s+)?(previous|above|prior|you were told)/gi,
|
|
57
|
+
/you\s+are\s+now\b/gi,
|
|
58
|
+
/reveal\s+(your|the)\s+(system\s+)?(instructions?|prompt|prompts?)/gi,
|
|
59
|
+
/(print|show|repeat)\s+(your|the)\s+(system\s+)?(instructions?|prompt)/gi,
|
|
60
|
+
/act\s+as\s+(an?\s+)?(system|admin|administrator|developer|root)\b/gi,
|
|
61
|
+
/<\/?(system|assistant|tool)\b[^>]*>/gi,
|
|
62
|
+
/\bBEGIN\s+SYSTEM\b/gi
|
|
63
|
+
];
|
|
64
|
+
function guardrailsEnabled() {
|
|
65
|
+
return process.env["DXCRM_GUARDRAILS"] === "on";
|
|
66
|
+
}
|
|
67
|
+
/** Replace known injection spans with a [filtered] marker, keeping other text. */
|
|
68
|
+
function neutralizeUntrusted(text) {
|
|
69
|
+
let out = text;
|
|
70
|
+
for (const re of INJECTION_PATTERNS) out = out.replace(re, "[filtered]");
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/core/compliance.ts
|
|
75
|
+
var compliance_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
76
|
+
aiDisclosure: () => aiDisclosure,
|
|
77
|
+
aiDisclosureEnabled: () => aiDisclosureEnabled,
|
|
78
|
+
labelAiContent: () => labelAiContent,
|
|
79
|
+
llmProvider: () => llmProvider,
|
|
80
|
+
localLlmConfig: () => localLlmConfig
|
|
81
|
+
});
|
|
82
|
+
const DISCLOSURES = {
|
|
83
|
+
de: "Hinweis: Dieser Inhalt wurde mithilfe von KI erstellt (EU-AI-Act Art. 50).",
|
|
84
|
+
en: "Note: This content was generated with the help of AI (EU AI Act Art. 50)."
|
|
85
|
+
};
|
|
86
|
+
/** Localized Art. 50 disclosure string. */
|
|
87
|
+
function aiDisclosure(lang = "de") {
|
|
88
|
+
return DISCLOSURES[lang] ?? DISCLOSURES.en;
|
|
89
|
+
}
|
|
90
|
+
/** Disclosure is ON by default (Art. 50); opt out with DXCRM_AI_DISCLOSURE=off. */
|
|
91
|
+
function aiDisclosureEnabled() {
|
|
92
|
+
return process.env["DXCRM_AI_DISCLOSURE"] !== "off";
|
|
93
|
+
}
|
|
94
|
+
/** Prepend the Art. 50 disclosure to generated content (unless disabled). */
|
|
95
|
+
function labelAiContent(text, opts = {}) {
|
|
96
|
+
if (!(opts.enabled ?? aiDisclosureEnabled())) return text;
|
|
97
|
+
return `${aiDisclosure(opts.lang ?? (process.env["DXCRM_AI_DISCLOSURE_LANG"] || "de"))}\n\n${text}`;
|
|
98
|
+
}
|
|
99
|
+
/** Resolve the configured LLM provider (default Anthropic). */
|
|
100
|
+
function llmProvider() {
|
|
101
|
+
const p = (process.env["DXCRM_LLM_PROVIDER"] ?? "anthropic").toLowerCase();
|
|
102
|
+
if (p === "ollama" || p === "openai" || p === "local") return p;
|
|
103
|
+
return "anthropic";
|
|
104
|
+
}
|
|
105
|
+
/** OpenAI-compatible local endpoint config (Ollama defaults), env-overridable. */
|
|
106
|
+
function localLlmConfig() {
|
|
107
|
+
return {
|
|
108
|
+
baseUrl: process.env["DXCRM_LLM_BASE_URL"] ?? "http://127.0.0.1:11434/v1",
|
|
109
|
+
model: process.env["DXCRM_LLM_MODEL"] ?? "llama3.1"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
Object.defineProperty(exports, "compliance_exports", {
|
|
114
|
+
enumerable: true,
|
|
115
|
+
get: function() {
|
|
116
|
+
return compliance_exports;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
Object.defineProperty(exports, "guardrailsEnabled", {
|
|
120
|
+
enumerable: true,
|
|
121
|
+
get: function() {
|
|
122
|
+
return guardrailsEnabled;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
Object.defineProperty(exports, "llmProvider", {
|
|
126
|
+
enumerable: true,
|
|
127
|
+
get: function() {
|
|
128
|
+
return llmProvider;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
Object.defineProperty(exports, "localLlmConfig", {
|
|
132
|
+
enumerable: true,
|
|
133
|
+
get: function() {
|
|
134
|
+
return localLlmConfig;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
Object.defineProperty(exports, "maskPii", {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
get: function() {
|
|
140
|
+
return maskPii;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
Object.defineProperty(exports, "neutralizeUntrusted", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
get: function() {
|
|
146
|
+
return neutralizeUntrusted;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
Object.defineProperty(exports, "piiMaskingEnabled", {
|
|
150
|
+
enumerable: true,
|
|
151
|
+
get: function() {
|
|
152
|
+
return piiMaskingEnabled;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=compliance-B91zNvCR.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-B91zNvCR.cjs","names":[],"sources":["../src/core/pii.ts","../src/core/guardrails.ts","../src/core/compliance.ts"],"sourcesContent":["/**\n * Lightweight PII masking for LLM calls (Einstein-Trust-Layer-style).\n * Replaces emails and phone numbers with stable placeholders before text is\n * sent to an LLM, and can restore them in the response. Opt-in via\n * DXCRM_PII_MASKING=on so default behaviour and quality are unchanged.\n */\n\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\n// Phone: optional +, then 7+ digits possibly grouped by spaces/dashes/parens.\nconst PHONE_RE = /\\+?\\d[\\d\\s().-]{6,}\\d/g;\n\nexport interface MaskedText {\n masked: string;\n unmask: (text: string) => string;\n}\n\nexport function piiMaskingEnabled(): boolean {\n return process.env[\"DXCRM_PII_MASKING\"] === \"on\";\n}\n\nexport function maskPii(input: string): MaskedText {\n const mapping = new Map<string, string>(); // original -> placeholder\n let emailCount = 0;\n let phoneCount = 0;\n\n let masked = input.replace(EMAIL_RE, (match) => {\n let ph = mapping.get(match);\n if (!ph) {\n ph = `[EMAIL_${emailCount++}]`;\n mapping.set(match, ph);\n }\n return ph;\n });\n\n masked = masked.replace(PHONE_RE, (match) => {\n // Ignore very short numeric runs that slipped through (already handled by {6,}).\n let ph = mapping.get(match);\n if (!ph) {\n ph = `[PHONE_${phoneCount++}]`;\n mapping.set(match, ph);\n }\n return ph;\n });\n\n const reverse = new Map<string, string>(); // placeholder -> original\n for (const [orig, ph] of mapping) reverse.set(ph, orig);\n\n const unmask = (text: string): string => {\n let out = text;\n for (const [ph, orig] of reverse) out = out.split(ph).join(orig);\n return out;\n };\n\n return { masked, unmask };\n}\n","/**\n * Guardrails against (indirect) prompt injection in untrusted CRM content\n * (emails, transcripts, imported notes). Detection is always available;\n * neutralization is applied to LLM inputs when DXCRM_GUARDRAILS=on.\n */\n\nconst INJECTION_PATTERNS: RegExp[] = [\n /ignore\\s+(all\\s+|the\\s+|any\\s+)?(previous|above|prior|preceding)\\s+(instructions?|prompts?|messages?)/gi,\n /disregard\\s+(all\\s+|the\\s+|any\\s+)?(previous|above|prior|preceding)/gi,\n /forget\\s+(all\\s+|the\\s+|everything\\s+)?(previous|above|prior|you were told)/gi,\n /you\\s+are\\s+now\\b/gi,\n /reveal\\s+(your|the)\\s+(system\\s+)?(instructions?|prompt|prompts?)/gi,\n /(print|show|repeat)\\s+(your|the)\\s+(system\\s+)?(instructions?|prompt)/gi,\n /act\\s+as\\s+(an?\\s+)?(system|admin|administrator|developer|root)\\b/gi,\n /<\\/?(system|assistant|tool)\\b[^>]*>/gi,\n /\\bBEGIN\\s+SYSTEM\\b/gi,\n];\n\nexport function guardrailsEnabled(): boolean {\n return process.env[\"DXCRM_GUARDRAILS\"] === \"on\";\n}\n\nexport function detectPromptInjection(text: string): { flagged: boolean; matches: string[] } {\n const matches: string[] = [];\n for (const re of INJECTION_PATTERNS) {\n re.lastIndex = 0;\n const found = text.match(re);\n if (found) matches.push(...found);\n }\n return { flagged: matches.length > 0, matches };\n}\n\n/** Replace known injection spans with a [filtered] marker, keeping other text. */\nexport function neutralizeUntrusted(text: string): string {\n let out = text;\n for (const re of INJECTION_PATTERNS) {\n out = out.replace(re, \"[filtered]\");\n }\n return out;\n}\n","import { piiMaskingEnabled } from \"./pii.js\";\nimport { guardrailsEnabled } from \"./guardrails.js\";\n\n/**\n * Compliance hardening (domino D17 / §3): the cross-cutting governance layer.\n * - EU-AI-Act Art. 50 transparency: a localized disclosure that AI generated\n * the content, on by default (opt-out), wrappable around any generated text.\n * - Provider-agnostic LLM selection so the workspace can run against a local,\n * self-hosted model (Ollama / OpenAI-compatible) as a data-residency moat —\n * no customer data leaves the machine. The provider runtime itself stays in\n * the agent framework; here we only resolve and expose the configuration.\n * - A single read-out of the active privacy/compliance posture.\n */\nexport type DisclosureLang = \"de\" | \"en\";\n\nexport type LlmProviderName = \"anthropic\" | \"ollama\" | \"openai\" | \"local\";\n\nconst DISCLOSURES: Record<DisclosureLang, string> = {\n de: \"Hinweis: Dieser Inhalt wurde mithilfe von KI erstellt (EU-AI-Act Art. 50).\",\n en: \"Note: This content was generated with the help of AI (EU AI Act Art. 50).\",\n};\n\n/** Localized Art. 50 disclosure string. */\nexport function aiDisclosure(lang: DisclosureLang = \"de\"): string {\n return DISCLOSURES[lang] ?? DISCLOSURES.en;\n}\n\n/** Disclosure is ON by default (Art. 50); opt out with DXCRM_AI_DISCLOSURE=off. */\nexport function aiDisclosureEnabled(): boolean {\n return process.env[\"DXCRM_AI_DISCLOSURE\"] !== \"off\";\n}\n\n/** Prepend the Art. 50 disclosure to generated content (unless disabled). */\nexport function labelAiContent(\n text: string,\n opts: { lang?: DisclosureLang; enabled?: boolean } = {}\n): string {\n const enabled = opts.enabled ?? aiDisclosureEnabled();\n if (!enabled) return text;\n const lang = opts.lang ?? ((process.env[\"DXCRM_AI_DISCLOSURE_LANG\"] as DisclosureLang) || \"de\");\n return `${aiDisclosure(lang)}\\n\\n${text}`;\n}\n\n/** Resolve the configured LLM provider (default Anthropic). */\nexport function llmProvider(): LlmProviderName {\n const p = (process.env[\"DXCRM_LLM_PROVIDER\"] ?? \"anthropic\").toLowerCase();\n if (p === \"ollama\" || p === \"openai\" || p === \"local\") return p;\n return \"anthropic\";\n}\n\nexport interface LocalLlmConfig {\n baseUrl: string;\n model: string;\n}\n\n/** OpenAI-compatible local endpoint config (Ollama defaults), env-overridable. */\nexport function localLlmConfig(): LocalLlmConfig {\n return {\n baseUrl: process.env[\"DXCRM_LLM_BASE_URL\"] ?? \"http://127.0.0.1:11434/v1\",\n model: process.env[\"DXCRM_LLM_MODEL\"] ?? \"llama3.1\",\n };\n}\n\nexport interface ComplianceConfig {\n provider: LlmProviderName;\n local: LocalLlmConfig | null;\n aiDisclosure: boolean;\n piiMasking: boolean;\n guardrails: boolean;\n}\n\n/** A single read-out of the active privacy/compliance posture. */\nexport function complianceConfig(): ComplianceConfig {\n const provider = llmProvider();\n return {\n provider,\n local: provider === \"anthropic\" ? null : localLlmConfig(),\n aiDisclosure: aiDisclosureEnabled(),\n piiMasking: piiMaskingEnabled(),\n guardrails: guardrailsEnabled(),\n };\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,WAAW;AAEjB,MAAM,WAAW;AAOjB,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,IAAI,yBAAyB;AAC9C;AAEA,SAAgB,QAAQ,OAA2B;CACjD,MAAM,0BAAU,IAAI,IAAoB;CACxC,IAAI,aAAa;CACjB,IAAI,aAAa;CAEjB,IAAI,SAAS,MAAM,QAAQ,WAAW,UAAU;EAC9C,IAAI,KAAK,QAAQ,IAAI,KAAK;EAC1B,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,aAAa;GAC5B,QAAQ,IAAI,OAAO,EAAE;EACvB;EACA,OAAO;CACT,CAAC;CAED,SAAS,OAAO,QAAQ,WAAW,UAAU;EAE3C,IAAI,KAAK,QAAQ,IAAI,KAAK;EAC1B,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,aAAa;GAC5B,QAAQ,IAAI,OAAO,EAAE;EACvB;EACA,OAAO;CACT,CAAC;CAED,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,CAAC,MAAM,OAAO,SAAS,QAAQ,IAAI,IAAI,IAAI;CAEtD,MAAM,UAAU,SAAyB;EACvC,IAAI,MAAM;EACV,KAAK,MAAM,CAAC,IAAI,SAAS,SAAS,MAAM,IAAI,MAAM,EAAE,EAAE,KAAK,IAAI;EAC/D,OAAO;CACT;CAEA,OAAO;EAAE;EAAQ;CAAO;AAC1B;;;;;;;;AChDA,MAAM,qBAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,IAAI,wBAAwB;AAC7C;;AAaA,SAAgB,oBAAoB,MAAsB;CACxD,IAAI,MAAM;CACV,KAAK,MAAM,MAAM,oBACf,MAAM,IAAI,QAAQ,IAAI,YAAY;CAEpC,OAAO;AACT;;;;;;;;;;ACtBA,MAAM,cAA8C;CAClD,IAAI;CACJ,IAAI;AACN;;AAGA,SAAgB,aAAa,OAAuB,MAAc;CAChE,OAAO,YAAY,SAAS,YAAY;AAC1C;;AAGA,SAAgB,sBAA+B;CAC7C,OAAO,QAAQ,IAAI,2BAA2B;AAChD;;AAGA,SAAgB,eACd,MACA,OAAqD,CAAC,GAC9C;CAER,IAAI,EADY,KAAK,WAAW,oBAAoB,IACtC,OAAO;CAErB,OAAO,GAAG,aADG,KAAK,SAAU,QAAQ,IAAI,+BAAkD,KAC/D,EAAE,MAAM;AACrC;;AAGA,SAAgB,cAA+B;CAC7C,MAAM,KAAK,QAAQ,IAAI,yBAAyB,aAAa,YAAY;CACzE,IAAI,MAAM,YAAY,MAAM,YAAY,MAAM,SAAS,OAAO;CAC9D,OAAO;AACT;;AAQA,SAAgB,iBAAiC;CAC/C,OAAO;EACL,SAAS,QAAQ,IAAI,yBAAyB;EAC9C,OAAO,QAAQ,IAAI,sBAAsB;CAC3C;AACF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/core/pii.ts
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight PII masking for LLM calls (Einstein-Trust-Layer-style).
|
|
4
|
+
* Replaces emails and phone numbers with stable placeholders before text is
|
|
5
|
+
* sent to an LLM, and can restore them in the response. Opt-in via
|
|
6
|
+
* DXCRM_PII_MASKING=on so default behaviour and quality are unchanged.
|
|
7
|
+
*/
|
|
8
|
+
const EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
9
|
+
const PHONE_RE = /\+?\d[\d\s().-]{6,}\d/g;
|
|
10
|
+
function piiMaskingEnabled() {
|
|
11
|
+
return process.env["DXCRM_PII_MASKING"] === "on";
|
|
12
|
+
}
|
|
13
|
+
function maskPii(input) {
|
|
14
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
15
|
+
let emailCount = 0;
|
|
16
|
+
let phoneCount = 0;
|
|
17
|
+
let masked = input.replace(EMAIL_RE, (match) => {
|
|
18
|
+
let ph = mapping.get(match);
|
|
19
|
+
if (!ph) {
|
|
20
|
+
ph = `[EMAIL_${emailCount++}]`;
|
|
21
|
+
mapping.set(match, ph);
|
|
22
|
+
}
|
|
23
|
+
return ph;
|
|
24
|
+
});
|
|
25
|
+
masked = masked.replace(PHONE_RE, (match) => {
|
|
26
|
+
let ph = mapping.get(match);
|
|
27
|
+
if (!ph) {
|
|
28
|
+
ph = `[PHONE_${phoneCount++}]`;
|
|
29
|
+
mapping.set(match, ph);
|
|
30
|
+
}
|
|
31
|
+
return ph;
|
|
32
|
+
});
|
|
33
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const [orig, ph] of mapping) reverse.set(ph, orig);
|
|
35
|
+
const unmask = (text) => {
|
|
36
|
+
let out = text;
|
|
37
|
+
for (const [ph, orig] of reverse) out = out.split(ph).join(orig);
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
masked,
|
|
42
|
+
unmask
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/core/guardrails.ts
|
|
47
|
+
/**
|
|
48
|
+
* Guardrails against (indirect) prompt injection in untrusted CRM content
|
|
49
|
+
* (emails, transcripts, imported notes). Detection is always available;
|
|
50
|
+
* neutralization is applied to LLM inputs when DXCRM_GUARDRAILS=on.
|
|
51
|
+
*/
|
|
52
|
+
const INJECTION_PATTERNS = [
|
|
53
|
+
/ignore\s+(all\s+|the\s+|any\s+)?(previous|above|prior|preceding)\s+(instructions?|prompts?|messages?)/gi,
|
|
54
|
+
/disregard\s+(all\s+|the\s+|any\s+)?(previous|above|prior|preceding)/gi,
|
|
55
|
+
/forget\s+(all\s+|the\s+|everything\s+)?(previous|above|prior|you were told)/gi,
|
|
56
|
+
/you\s+are\s+now\b/gi,
|
|
57
|
+
/reveal\s+(your|the)\s+(system\s+)?(instructions?|prompt|prompts?)/gi,
|
|
58
|
+
/(print|show|repeat)\s+(your|the)\s+(system\s+)?(instructions?|prompt)/gi,
|
|
59
|
+
/act\s+as\s+(an?\s+)?(system|admin|administrator|developer|root)\b/gi,
|
|
60
|
+
/<\/?(system|assistant|tool)\b[^>]*>/gi,
|
|
61
|
+
/\bBEGIN\s+SYSTEM\b/gi
|
|
62
|
+
];
|
|
63
|
+
function guardrailsEnabled() {
|
|
64
|
+
return process.env["DXCRM_GUARDRAILS"] === "on";
|
|
65
|
+
}
|
|
66
|
+
/** Replace known injection spans with a [filtered] marker, keeping other text. */
|
|
67
|
+
function neutralizeUntrusted(text) {
|
|
68
|
+
let out = text;
|
|
69
|
+
for (const re of INJECTION_PATTERNS) out = out.replace(re, "[filtered]");
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/core/compliance.ts
|
|
74
|
+
const DISCLOSURES = {
|
|
75
|
+
de: "Hinweis: Dieser Inhalt wurde mithilfe von KI erstellt (EU-AI-Act Art. 50).",
|
|
76
|
+
en: "Note: This content was generated with the help of AI (EU AI Act Art. 50)."
|
|
77
|
+
};
|
|
78
|
+
/** Localized Art. 50 disclosure string. */
|
|
79
|
+
function aiDisclosure(lang = "de") {
|
|
80
|
+
return DISCLOSURES[lang] ?? DISCLOSURES.en;
|
|
81
|
+
}
|
|
82
|
+
/** Disclosure is ON by default (Art. 50); opt out with DXCRM_AI_DISCLOSURE=off. */
|
|
83
|
+
function aiDisclosureEnabled() {
|
|
84
|
+
return process.env["DXCRM_AI_DISCLOSURE"] !== "off";
|
|
85
|
+
}
|
|
86
|
+
/** Prepend the Art. 50 disclosure to generated content (unless disabled). */
|
|
87
|
+
function labelAiContent(text, opts = {}) {
|
|
88
|
+
if (!(opts.enabled ?? aiDisclosureEnabled())) return text;
|
|
89
|
+
return `${aiDisclosure(opts.lang ?? (process.env["DXCRM_AI_DISCLOSURE_LANG"] || "de"))}\n\n${text}`;
|
|
90
|
+
}
|
|
91
|
+
/** Resolve the configured LLM provider (default Anthropic). */
|
|
92
|
+
function llmProvider() {
|
|
93
|
+
const p = (process.env["DXCRM_LLM_PROVIDER"] ?? "anthropic").toLowerCase();
|
|
94
|
+
if (p === "ollama" || p === "openai" || p === "local") return p;
|
|
95
|
+
return "anthropic";
|
|
96
|
+
}
|
|
97
|
+
/** OpenAI-compatible local endpoint config (Ollama defaults), env-overridable. */
|
|
98
|
+
function localLlmConfig() {
|
|
99
|
+
return {
|
|
100
|
+
baseUrl: process.env["DXCRM_LLM_BASE_URL"] ?? "http://127.0.0.1:11434/v1",
|
|
101
|
+
model: process.env["DXCRM_LLM_MODEL"] ?? "llama3.1"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** A single read-out of the active privacy/compliance posture. */
|
|
105
|
+
function complianceConfig() {
|
|
106
|
+
const provider = llmProvider();
|
|
107
|
+
return {
|
|
108
|
+
provider,
|
|
109
|
+
local: provider === "anthropic" ? null : localLlmConfig(),
|
|
110
|
+
aiDisclosure: aiDisclosureEnabled(),
|
|
111
|
+
piiMasking: piiMaskingEnabled(),
|
|
112
|
+
guardrails: guardrailsEnabled()
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { llmProvider as a, neutralizeUntrusted as c, labelAiContent as i, maskPii as l, aiDisclosureEnabled as n, localLlmConfig as o, complianceConfig as r, guardrailsEnabled as s, aiDisclosure as t, piiMaskingEnabled as u };
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=compliance-CKSBoQUe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-CKSBoQUe.js","names":[],"sources":["../src/core/pii.ts","../src/core/guardrails.ts","../src/core/compliance.ts"],"sourcesContent":["/**\n * Lightweight PII masking for LLM calls (Einstein-Trust-Layer-style).\n * Replaces emails and phone numbers with stable placeholders before text is\n * sent to an LLM, and can restore them in the response. Opt-in via\n * DXCRM_PII_MASKING=on so default behaviour and quality are unchanged.\n */\n\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\n// Phone: optional +, then 7+ digits possibly grouped by spaces/dashes/parens.\nconst PHONE_RE = /\\+?\\d[\\d\\s().-]{6,}\\d/g;\n\nexport interface MaskedText {\n masked: string;\n unmask: (text: string) => string;\n}\n\nexport function piiMaskingEnabled(): boolean {\n return process.env[\"DXCRM_PII_MASKING\"] === \"on\";\n}\n\nexport function maskPii(input: string): MaskedText {\n const mapping = new Map<string, string>(); // original -> placeholder\n let emailCount = 0;\n let phoneCount = 0;\n\n let masked = input.replace(EMAIL_RE, (match) => {\n let ph = mapping.get(match);\n if (!ph) {\n ph = `[EMAIL_${emailCount++}]`;\n mapping.set(match, ph);\n }\n return ph;\n });\n\n masked = masked.replace(PHONE_RE, (match) => {\n // Ignore very short numeric runs that slipped through (already handled by {6,}).\n let ph = mapping.get(match);\n if (!ph) {\n ph = `[PHONE_${phoneCount++}]`;\n mapping.set(match, ph);\n }\n return ph;\n });\n\n const reverse = new Map<string, string>(); // placeholder -> original\n for (const [orig, ph] of mapping) reverse.set(ph, orig);\n\n const unmask = (text: string): string => {\n let out = text;\n for (const [ph, orig] of reverse) out = out.split(ph).join(orig);\n return out;\n };\n\n return { masked, unmask };\n}\n","/**\n * Guardrails against (indirect) prompt injection in untrusted CRM content\n * (emails, transcripts, imported notes). Detection is always available;\n * neutralization is applied to LLM inputs when DXCRM_GUARDRAILS=on.\n */\n\nconst INJECTION_PATTERNS: RegExp[] = [\n /ignore\\s+(all\\s+|the\\s+|any\\s+)?(previous|above|prior|preceding)\\s+(instructions?|prompts?|messages?)/gi,\n /disregard\\s+(all\\s+|the\\s+|any\\s+)?(previous|above|prior|preceding)/gi,\n /forget\\s+(all\\s+|the\\s+|everything\\s+)?(previous|above|prior|you were told)/gi,\n /you\\s+are\\s+now\\b/gi,\n /reveal\\s+(your|the)\\s+(system\\s+)?(instructions?|prompt|prompts?)/gi,\n /(print|show|repeat)\\s+(your|the)\\s+(system\\s+)?(instructions?|prompt)/gi,\n /act\\s+as\\s+(an?\\s+)?(system|admin|administrator|developer|root)\\b/gi,\n /<\\/?(system|assistant|tool)\\b[^>]*>/gi,\n /\\bBEGIN\\s+SYSTEM\\b/gi,\n];\n\nexport function guardrailsEnabled(): boolean {\n return process.env[\"DXCRM_GUARDRAILS\"] === \"on\";\n}\n\nexport function detectPromptInjection(text: string): { flagged: boolean; matches: string[] } {\n const matches: string[] = [];\n for (const re of INJECTION_PATTERNS) {\n re.lastIndex = 0;\n const found = text.match(re);\n if (found) matches.push(...found);\n }\n return { flagged: matches.length > 0, matches };\n}\n\n/** Replace known injection spans with a [filtered] marker, keeping other text. */\nexport function neutralizeUntrusted(text: string): string {\n let out = text;\n for (const re of INJECTION_PATTERNS) {\n out = out.replace(re, \"[filtered]\");\n }\n return out;\n}\n","import { piiMaskingEnabled } from \"./pii.js\";\nimport { guardrailsEnabled } from \"./guardrails.js\";\n\n/**\n * Compliance hardening (domino D17 / §3): the cross-cutting governance layer.\n * - EU-AI-Act Art. 50 transparency: a localized disclosure that AI generated\n * the content, on by default (opt-out), wrappable around any generated text.\n * - Provider-agnostic LLM selection so the workspace can run against a local,\n * self-hosted model (Ollama / OpenAI-compatible) as a data-residency moat —\n * no customer data leaves the machine. The provider runtime itself stays in\n * the agent framework; here we only resolve and expose the configuration.\n * - A single read-out of the active privacy/compliance posture.\n */\nexport type DisclosureLang = \"de\" | \"en\";\n\nexport type LlmProviderName = \"anthropic\" | \"ollama\" | \"openai\" | \"local\";\n\nconst DISCLOSURES: Record<DisclosureLang, string> = {\n de: \"Hinweis: Dieser Inhalt wurde mithilfe von KI erstellt (EU-AI-Act Art. 50).\",\n en: \"Note: This content was generated with the help of AI (EU AI Act Art. 50).\",\n};\n\n/** Localized Art. 50 disclosure string. */\nexport function aiDisclosure(lang: DisclosureLang = \"de\"): string {\n return DISCLOSURES[lang] ?? DISCLOSURES.en;\n}\n\n/** Disclosure is ON by default (Art. 50); opt out with DXCRM_AI_DISCLOSURE=off. */\nexport function aiDisclosureEnabled(): boolean {\n return process.env[\"DXCRM_AI_DISCLOSURE\"] !== \"off\";\n}\n\n/** Prepend the Art. 50 disclosure to generated content (unless disabled). */\nexport function labelAiContent(\n text: string,\n opts: { lang?: DisclosureLang; enabled?: boolean } = {}\n): string {\n const enabled = opts.enabled ?? aiDisclosureEnabled();\n if (!enabled) return text;\n const lang = opts.lang ?? ((process.env[\"DXCRM_AI_DISCLOSURE_LANG\"] as DisclosureLang) || \"de\");\n return `${aiDisclosure(lang)}\\n\\n${text}`;\n}\n\n/** Resolve the configured LLM provider (default Anthropic). */\nexport function llmProvider(): LlmProviderName {\n const p = (process.env[\"DXCRM_LLM_PROVIDER\"] ?? \"anthropic\").toLowerCase();\n if (p === \"ollama\" || p === \"openai\" || p === \"local\") return p;\n return \"anthropic\";\n}\n\nexport interface LocalLlmConfig {\n baseUrl: string;\n model: string;\n}\n\n/** OpenAI-compatible local endpoint config (Ollama defaults), env-overridable. */\nexport function localLlmConfig(): LocalLlmConfig {\n return {\n baseUrl: process.env[\"DXCRM_LLM_BASE_URL\"] ?? \"http://127.0.0.1:11434/v1\",\n model: process.env[\"DXCRM_LLM_MODEL\"] ?? \"llama3.1\",\n };\n}\n\nexport interface ComplianceConfig {\n provider: LlmProviderName;\n local: LocalLlmConfig | null;\n aiDisclosure: boolean;\n piiMasking: boolean;\n guardrails: boolean;\n}\n\n/** A single read-out of the active privacy/compliance posture. */\nexport function complianceConfig(): ComplianceConfig {\n const provider = llmProvider();\n return {\n provider,\n local: provider === \"anthropic\" ? null : localLlmConfig(),\n aiDisclosure: aiDisclosureEnabled(),\n piiMasking: piiMaskingEnabled(),\n guardrails: guardrailsEnabled(),\n };\n}\n"],"mappings":";;;;;;;AAOA,MAAM,WAAW;AAEjB,MAAM,WAAW;AAOjB,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,IAAI,yBAAyB;AAC9C;AAEA,SAAgB,QAAQ,OAA2B;CACjD,MAAM,0BAAU,IAAI,IAAoB;CACxC,IAAI,aAAa;CACjB,IAAI,aAAa;CAEjB,IAAI,SAAS,MAAM,QAAQ,WAAW,UAAU;EAC9C,IAAI,KAAK,QAAQ,IAAI,KAAK;EAC1B,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,aAAa;GAC5B,QAAQ,IAAI,OAAO,EAAE;EACvB;EACA,OAAO;CACT,CAAC;CAED,SAAS,OAAO,QAAQ,WAAW,UAAU;EAE3C,IAAI,KAAK,QAAQ,IAAI,KAAK;EAC1B,IAAI,CAAC,IAAI;GACP,KAAK,UAAU,aAAa;GAC5B,QAAQ,IAAI,OAAO,EAAE;EACvB;EACA,OAAO;CACT,CAAC;CAED,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,CAAC,MAAM,OAAO,SAAS,QAAQ,IAAI,IAAI,IAAI;CAEtD,MAAM,UAAU,SAAyB;EACvC,IAAI,MAAM;EACV,KAAK,MAAM,CAAC,IAAI,SAAS,SAAS,MAAM,IAAI,MAAM,EAAE,EAAE,KAAK,IAAI;EAC/D,OAAO;CACT;CAEA,OAAO;EAAE;EAAQ;CAAO;AAC1B;;;;;;;;AChDA,MAAM,qBAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,IAAI,wBAAwB;AAC7C;;AAaA,SAAgB,oBAAoB,MAAsB;CACxD,IAAI,MAAM;CACV,KAAK,MAAM,MAAM,oBACf,MAAM,IAAI,QAAQ,IAAI,YAAY;CAEpC,OAAO;AACT;;;ACtBA,MAAM,cAA8C;CAClD,IAAI;CACJ,IAAI;AACN;;AAGA,SAAgB,aAAa,OAAuB,MAAc;CAChE,OAAO,YAAY,SAAS,YAAY;AAC1C;;AAGA,SAAgB,sBAA+B;CAC7C,OAAO,QAAQ,IAAI,2BAA2B;AAChD;;AAGA,SAAgB,eACd,MACA,OAAqD,CAAC,GAC9C;CAER,IAAI,EADY,KAAK,WAAW,oBAAoB,IACtC,OAAO;CAErB,OAAO,GAAG,aADG,KAAK,SAAU,QAAQ,IAAI,+BAAkD,KAC/D,EAAE,MAAM;AACrC;;AAGA,SAAgB,cAA+B;CAC7C,MAAM,KAAK,QAAQ,IAAI,yBAAyB,aAAa,YAAY;CACzE,IAAI,MAAM,YAAY,MAAM,YAAY,MAAM,SAAS,OAAO;CAC9D,OAAO;AACT;;AAQA,SAAgB,iBAAiC;CAC/C,OAAO;EACL,SAAS,QAAQ,IAAI,yBAAyB;EAC9C,OAAO,QAAQ,IAAI,sBAAsB;CAC3C;AACF;;AAWA,SAAgB,mBAAqC;CACnD,MAAM,WAAW,YAAY;CAC7B,OAAO;EACL;EACA,OAAO,aAAa,cAAc,OAAO,eAAe;EACxD,cAAc,oBAAoB;EAClC,YAAY,kBAAkB;EAC9B,YAAY,kBAAkB;CAChC;AACF"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
//#region src/core/context-builder.ts
|
|
5
|
+
const MAX_INTERACTIONS = 10;
|
|
6
|
+
function estimateTokens(text) {
|
|
7
|
+
return Math.ceil(text.length / 4);
|
|
8
|
+
}
|
|
9
|
+
function parseRecentInteractions(filePath, limit) {
|
|
10
|
+
if (!fs.existsSync(filePath)) return "";
|
|
11
|
+
return fs.readFileSync(filePath, "utf-8").split(/(?=^## \d{4}-\d{2}-\d{2})/m).filter((e) => e.trim()).slice(0, limit).join("\n").trim();
|
|
12
|
+
}
|
|
13
|
+
function parsePipelineContent(filePath) {
|
|
14
|
+
if (!fs.existsSync(filePath)) return "";
|
|
15
|
+
return fs.readFileSync(filePath, "utf-8").trim();
|
|
16
|
+
}
|
|
17
|
+
function extractSection(content, sectionName) {
|
|
18
|
+
const match = new RegExp(`## ${sectionName}([\\s\\S]*?)(?=^## |$)`, "m").exec(content);
|
|
19
|
+
return match ? (match[1] ?? "").trim() : "";
|
|
20
|
+
}
|
|
21
|
+
async function buildContext(dataDir, slug) {
|
|
22
|
+
const customerDir = path.join(dataDir, "customers", slug);
|
|
23
|
+
if (!fs.existsSync(customerDir)) throw new Error(`Customer '${slug}' not found`);
|
|
24
|
+
const mainFactsPath = path.join(customerDir, "main_facts.md");
|
|
25
|
+
const interactionsPath = path.join(customerDir, "interactions.md");
|
|
26
|
+
const pipelinePath = path.join(customerDir, "pipeline.md");
|
|
27
|
+
let mainContent = "";
|
|
28
|
+
let frontmatterStr = "";
|
|
29
|
+
if (fs.existsSync(mainFactsPath)) {
|
|
30
|
+
const raw = matter(fs.readFileSync(mainFactsPath, "utf-8"));
|
|
31
|
+
mainContent = raw.content ?? "";
|
|
32
|
+
frontmatterStr = Object.entries(raw.data).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join("\n");
|
|
33
|
+
}
|
|
34
|
+
const quickRef = extractSection(mainContent, "Quick Reference");
|
|
35
|
+
const contacts = extractSection(mainContent, "Contacts");
|
|
36
|
+
const criticalContext = extractSection(mainContent, "Critical Context");
|
|
37
|
+
const openQuestions = extractSection(mainContent, "Open Questions");
|
|
38
|
+
const recentActivity = parseRecentInteractions(interactionsPath, MAX_INTERACTIONS);
|
|
39
|
+
const pipelineContent = parsePipelineContent(pipelinePath);
|
|
40
|
+
const raw = [
|
|
41
|
+
`# Customer Context: ${slug}`,
|
|
42
|
+
"",
|
|
43
|
+
"## Metadata",
|
|
44
|
+
frontmatterStr || "(no metadata)",
|
|
45
|
+
"",
|
|
46
|
+
"## Quick Reference",
|
|
47
|
+
quickRef || "(not set)",
|
|
48
|
+
"",
|
|
49
|
+
"## Contacts",
|
|
50
|
+
contacts || "(not set)",
|
|
51
|
+
"",
|
|
52
|
+
"## Critical Context",
|
|
53
|
+
criticalContext || "(not set)",
|
|
54
|
+
"",
|
|
55
|
+
"## Recent Activity (last 10 interactions)",
|
|
56
|
+
recentActivity || "(no interactions yet)",
|
|
57
|
+
"",
|
|
58
|
+
"## Pipeline",
|
|
59
|
+
pipelineContent || "(no deals)",
|
|
60
|
+
"",
|
|
61
|
+
"## Open Questions",
|
|
62
|
+
openQuestions || "(none)"
|
|
63
|
+
].join("\n");
|
|
64
|
+
if (estimateTokens(raw) > 3e3) {
|
|
65
|
+
const trimmedActivity = parseRecentInteractions(interactionsPath, 5);
|
|
66
|
+
return [
|
|
67
|
+
`# Customer Context: ${slug}`,
|
|
68
|
+
"",
|
|
69
|
+
"## Metadata",
|
|
70
|
+
frontmatterStr || "(no metadata)",
|
|
71
|
+
"",
|
|
72
|
+
"## Quick Reference",
|
|
73
|
+
quickRef || "(not set)",
|
|
74
|
+
"",
|
|
75
|
+
"## Contacts",
|
|
76
|
+
contacts || "(not set)",
|
|
77
|
+
"",
|
|
78
|
+
"## Critical Context",
|
|
79
|
+
criticalContext || "(not set)",
|
|
80
|
+
"",
|
|
81
|
+
"## Recent Activity (last 5 interactions — trimmed for token budget)",
|
|
82
|
+
trimmedActivity || "(no interactions yet)",
|
|
83
|
+
"",
|
|
84
|
+
"## Pipeline",
|
|
85
|
+
pipelineContent || "(no deals)",
|
|
86
|
+
"",
|
|
87
|
+
"## Open Questions",
|
|
88
|
+
openQuestions || "(none)"
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
91
|
+
return raw;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { buildContext as t };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=context-builder-BzWAp3Zs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-builder-BzWAp3Zs.js","names":[],"sources":["../src/core/context-builder.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\n\nconst MAX_INTERACTIONS = 10;\n\nfunction estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction parseRecentInteractions(filePath: string, limit: number): string {\n if (!fs.existsSync(filePath)) return \"\";\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n\n // Split on ## date headings\n const entries = content.split(/(?=^## \\d{4}-\\d{2}-\\d{2})/m).filter((e) => e.trim());\n const recent = entries.slice(0, limit);\n return recent.join(\"\\n\").trim();\n}\n\nfunction parsePipelineContent(filePath: string): string {\n if (!fs.existsSync(filePath)) return \"\";\n const content = fs.readFileSync(filePath, \"utf-8\") as string;\n return content.trim();\n}\n\nfunction extractSection(content: string, sectionName: string): string {\n const regex = new RegExp(`## ${sectionName}([\\\\s\\\\S]*?)(?=^## |$)`, \"m\");\n const match = regex.exec(content);\n return match ? (match[1] ?? \"\").trim() : \"\";\n}\n\nexport async function buildContext(dataDir: string, slug: string): Promise<string> {\n const customerDir = path.join(dataDir, \"customers\", slug);\n\n if (!fs.existsSync(customerDir)) {\n throw new Error(`Customer '${slug}' not found`);\n }\n\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n const interactionsPath = path.join(customerDir, \"interactions.md\");\n const pipelinePath = path.join(customerDir, \"pipeline.md\");\n\n // Read main_facts.md\n let mainContent = \"\";\n let frontmatterStr = \"\";\n if (fs.existsSync(mainFactsPath)) {\n const fileContent = fs.readFileSync(mainFactsPath, \"utf-8\") as string;\n const raw = matter(fileContent);\n mainContent = raw.content ?? \"\";\n frontmatterStr = Object.entries(raw.data as Record<string, unknown>)\n .map(([k, v]) => `${k}: ${JSON.stringify(v)}`)\n .join(\"\\n\");\n }\n\n const quickRef = extractSection(mainContent, \"Quick Reference\");\n const contacts = extractSection(mainContent, \"Contacts\");\n const criticalContext = extractSection(mainContent, \"Critical Context\");\n const openQuestions = extractSection(mainContent, \"Open Questions\");\n\n const recentActivity = parseRecentInteractions(interactionsPath, MAX_INTERACTIONS);\n const pipelineContent = parsePipelineContent(pipelinePath);\n\n const sections: string[] = [\n `# Customer Context: ${slug}`,\n \"\",\n \"## Metadata\",\n frontmatterStr || \"(no metadata)\",\n \"\",\n \"## Quick Reference\",\n quickRef || \"(not set)\",\n \"\",\n \"## Contacts\",\n contacts || \"(not set)\",\n \"\",\n \"## Critical Context\",\n criticalContext || \"(not set)\",\n \"\",\n \"## Recent Activity (last 10 interactions)\",\n recentActivity || \"(no interactions yet)\",\n \"\",\n \"## Pipeline\",\n pipelineContent || \"(no deals)\",\n \"\",\n \"## Open Questions\",\n openQuestions || \"(none)\",\n ];\n\n const raw = sections.join(\"\\n\");\n const tokenEstimate = estimateTokens(raw);\n\n // If over 3000 tokens, trim interactions\n if (tokenEstimate > 3000) {\n const trimmedActivity = parseRecentInteractions(interactionsPath, 5);\n const trimmedSections: string[] = [\n `# Customer Context: ${slug}`,\n \"\",\n \"## Metadata\",\n frontmatterStr || \"(no metadata)\",\n \"\",\n \"## Quick Reference\",\n quickRef || \"(not set)\",\n \"\",\n \"## Contacts\",\n contacts || \"(not set)\",\n \"\",\n \"## Critical Context\",\n criticalContext || \"(not set)\",\n \"\",\n \"## Recent Activity (last 5 interactions — trimmed for token budget)\",\n trimmedActivity || \"(no interactions yet)\",\n \"\",\n \"## Pipeline\",\n pipelineContent || \"(no deals)\",\n \"\",\n \"## Open Questions\",\n openQuestions || \"(none)\",\n ];\n return trimmedSections.join(\"\\n\");\n }\n\n return raw;\n}\n\n/** Robust section-body extractor: from a `## Name` heading to the next `## ` heading. */\nfunction sectionBody(content: string, name: string): string {\n const lines = content.split(\"\\n\");\n const start = lines.findIndex((l) => l.trim() === `## ${name}`);\n if (start < 0) return \"\";\n const body: string[] = [];\n for (let i = start + 1; i < lines.length; i++) {\n if (lines[i]!.startsWith(\"## \")) break;\n body.push(lines[i]!);\n }\n return body.join(\"\\n\").trim();\n}\n\nexport interface ContextBlock {\n slug: string;\n metadata: Record<string, unknown>;\n quickReference: string;\n contacts: string;\n criticalContext: string;\n openQuestions: string;\n recentActivity: string;\n pipeline: string;\n}\n\n/**\n * Structured variant of buildContext (REF-2): returns a typed object instead of\n * a markdown string, for callers that need fields programmatically (e.g. MCP\n * responses, SDK consumers). buildContext remains the token-budgeted string form.\n */\nexport async function buildContextBlock(\n dataDir: string,\n slug: string,\n role?: \"admin\" | \"manager\" | \"rep\"\n): Promise<ContextBlock> {\n const customerDir = path.join(dataDir, \"customers\", slug);\n if (!fs.existsSync(customerDir)) {\n throw new Error(`Customer '${slug}' not found`);\n }\n\n const mainFactsPath = path.join(customerDir, \"main_facts.md\");\n const interactionsPath = path.join(customerDir, \"interactions.md\");\n const pipelinePath = path.join(customerDir, \"pipeline.md\");\n\n let mainContent = \"\";\n let metadata: Record<string, unknown> = {};\n if (fs.existsSync(mainFactsPath)) {\n const raw = matter(fs.readFileSync(mainFactsPath, \"utf-8\") as string);\n mainContent = raw.content ?? \"\";\n metadata = raw.data as Record<string, unknown>;\n }\n\n // Field-level security: redact metadata fields the role may not see.\n if (role) {\n const { loadFieldAcl, redactFields } = await import(\"./rbac.js\");\n metadata = redactFields(metadata, role, loadFieldAcl(dataDir));\n }\n\n return {\n slug,\n metadata,\n quickReference: sectionBody(mainContent, \"Quick Reference\"),\n contacts: sectionBody(mainContent, \"Contacts\"),\n criticalContext: sectionBody(mainContent, \"Critical Context\"),\n openQuestions: sectionBody(mainContent, \"Open Questions\"),\n recentActivity: parseRecentInteractions(interactionsPath, MAX_INTERACTIONS),\n pipeline: parsePipelineContent(pipelinePath),\n };\n}\n"],"mappings":";;;;AAIA,MAAM,mBAAmB;AAEzB,SAAS,eAAe,MAAsB;CAC5C,OAAO,KAAK,KAAK,KAAK,SAAS,CAAC;AAClC;AAEA,SAAS,wBAAwB,UAAkB,OAAuB;CACxE,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO;CAMrC,OALgB,GAAG,aAAa,UAAU,OAGpB,EAAE,MAAM,4BAA4B,EAAE,QAAQ,MAAM,EAAE,KAAK,CAC5D,EAAE,MAAM,GAAG,KACpB,EAAE,KAAK,IAAI,EAAE,KAAK;AAChC;AAEA,SAAS,qBAAqB,UAA0B;CACtD,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO;CAErC,OADgB,GAAG,aAAa,UAAU,OAC7B,EAAE,KAAK;AACtB;AAEA,SAAS,eAAe,SAAiB,aAA6B;CAEpE,MAAM,QAAQ,IADI,OAAO,MAAM,YAAY,yBAAyB,GAClD,EAAE,KAAK,OAAO;CAChC,OAAO,SAAS,MAAM,MAAM,IAAI,KAAK,IAAI;AAC3C;AAEA,eAAsB,aAAa,SAAiB,MAA+B;CACjF,MAAM,cAAc,KAAK,KAAK,SAAS,aAAa,IAAI;CAExD,IAAI,CAAC,GAAG,WAAW,WAAW,GAC5B,MAAM,IAAI,MAAM,aAAa,KAAK,YAAY;CAGhD,MAAM,gBAAgB,KAAK,KAAK,aAAa,eAAe;CAC5D,MAAM,mBAAmB,KAAK,KAAK,aAAa,iBAAiB;CACjE,MAAM,eAAe,KAAK,KAAK,aAAa,aAAa;CAGzD,IAAI,cAAc;CAClB,IAAI,iBAAiB;CACrB,IAAI,GAAG,WAAW,aAAa,GAAG;EAEhC,MAAM,MAAM,OADQ,GAAG,aAAa,eAAe,OACtB,CAAC;EAC9B,cAAc,IAAI,WAAW;EAC7B,iBAAiB,OAAO,QAAQ,IAAI,IAA+B,EAChE,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC,GAAG,EAC5C,KAAK,IAAI;CACd;CAEA,MAAM,WAAW,eAAe,aAAa,iBAAiB;CAC9D,MAAM,WAAW,eAAe,aAAa,UAAU;CACvD,MAAM,kBAAkB,eAAe,aAAa,kBAAkB;CACtE,MAAM,gBAAgB,eAAe,aAAa,gBAAgB;CAElE,MAAM,iBAAiB,wBAAwB,kBAAkB,gBAAgB;CACjF,MAAM,kBAAkB,qBAAqB,YAAY;CA2BzD,MAAM,MAAM;EAxBV,uBAAuB;EACvB;EACA;EACA,kBAAkB;EAClB;EACA;EACA,YAAY;EACZ;EACA;EACA,YAAY;EACZ;EACA;EACA,mBAAmB;EACnB;EACA;EACA,kBAAkB;EAClB;EACA;EACA,mBAAmB;EACnB;EACA;EACA,iBAAiB;CAGA,EAAE,KAAK,IAAI;CAI9B,IAHsB,eAAe,GAGrB,IAAI,KAAM;EACxB,MAAM,kBAAkB,wBAAwB,kBAAkB,CAAC;EAyBnE,OAAO;GAvBL,uBAAuB;GACvB;GACA;GACA,kBAAkB;GAClB;GACA;GACA,YAAY;GACZ;GACA;GACA,YAAY;GACZ;GACA;GACA,mBAAmB;GACnB;GACA;GACA,mBAAmB;GACnB;GACA;GACA,mBAAmB;GACnB;GACA;GACA,iBAAiB;EAEE,EAAE,KAAK,IAAI;CAClC;CAEA,OAAO;AACT"}
|