thumbgate 1.27.16 → 1.27.18

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.well-known/llms.txt +12 -33
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +30 -215
  6. package/adapters/chatgpt/openapi.yaml +1695 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gemini/function-declarations.json +0 -1
  9. package/adapters/mcp/server-stdio.js +11 -263
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +122 -1421
  13. package/bin/postinstall.js +27 -11
  14. package/config/gate-templates.json +0 -216
  15. package/config/gates/claim-verification.json +0 -12
  16. package/config/gates/default.json +2 -31
  17. package/config/github-about.json +2 -2
  18. package/config/mcp-allowlists.json +13 -23
  19. package/config/merge-quality-checks.json +1 -0
  20. package/config/model-candidates.json +6 -121
  21. package/openapi/openapi.yaml +0 -12
  22. package/package.json +98 -228
  23. package/public/blog.html +4 -4
  24. package/public/codex-plugin.html +20 -72
  25. package/public/compare.html +8 -31
  26. package/public/dashboard.html +166 -930
  27. package/public/federal.html +2 -2
  28. package/public/guide.html +9 -33
  29. package/public/index.html +116 -495
  30. package/public/learn.html +18 -191
  31. package/public/lessons.html +10 -168
  32. package/public/numbers.html +7 -7
  33. package/public/pro.html +11 -34
  34. package/scripts/agent-memory-lifecycle.js +0 -211
  35. package/scripts/agent-readiness.js +3 -167
  36. package/scripts/agent-reward-model.js +1 -53
  37. package/scripts/auto-promote-gates.js +10 -82
  38. package/scripts/auto-wire-hooks.js +0 -14
  39. package/scripts/billing.js +1 -93
  40. package/scripts/build-metadata.js +10 -50
  41. package/scripts/cli-feedback.js +6 -18
  42. package/scripts/cli-schema.js +0 -90
  43. package/scripts/cli-telemetry.js +1 -6
  44. package/scripts/commercial-offer.js +2 -82
  45. package/scripts/context-manager.js +6 -74
  46. package/scripts/dashboard.js +2 -68
  47. package/scripts/durability/step.js +3 -3
  48. package/scripts/export-databricks-bundle.js +1 -5
  49. package/scripts/export-dpo-pairs.js +2 -7
  50. package/scripts/feedback-loop.js +1 -123
  51. package/scripts/feedback-quality.js +0 -87
  52. package/scripts/filesystem-search.js +10 -35
  53. package/scripts/gate-stats.js +0 -89
  54. package/scripts/gates-engine.js +85 -1105
  55. package/scripts/gemini-embedding-policy.js +1 -2
  56. package/scripts/hook-runtime.js +14 -20
  57. package/scripts/hook-thumbgate-cache-updater.js +2 -18
  58. package/scripts/hybrid-feedback-context.js +7 -142
  59. package/scripts/lesson-inference.js +3 -8
  60. package/scripts/lesson-search.js +1 -17
  61. package/scripts/license.js +1 -0
  62. package/scripts/llm-client.js +9 -390
  63. package/scripts/local-model-profile.js +8 -15
  64. package/scripts/memory-scope-readiness.js +0 -159
  65. package/scripts/meta-agent-loop.js +0 -36
  66. package/scripts/operational-integrity.js +5 -39
  67. package/scripts/oss-pr-opportunity-scout.js +5 -35
  68. package/scripts/plausible-server-events.js +6 -9
  69. package/scripts/pro-local-dashboard.js +4 -4
  70. package/scripts/proxy-pointer-rag-guardrails.js +1 -42
  71. package/scripts/rate-limiter.js +13 -64
  72. package/scripts/secret-scanner.js +5 -44
  73. package/scripts/security-scanner.js +10 -260
  74. package/scripts/self-distill-agent.js +1 -3
  75. package/scripts/seo-gsd.js +2 -916
  76. package/scripts/statusline-cache-path.js +2 -17
  77. package/scripts/statusline-local-stats.js +1 -9
  78. package/scripts/statusline-meta.js +2 -28
  79. package/scripts/statusline.sh +4 -20
  80. package/scripts/telemetry-analytics.js +0 -357
  81. package/scripts/thompson-sampling.js +10 -31
  82. package/scripts/thumbgate-bench.js +1 -16
  83. package/scripts/thumbgate-search.js +19 -85
  84. package/scripts/tool-registry.js +1 -169
  85. package/scripts/vector-store.js +0 -45
  86. package/scripts/workflow-sentinel.js +53 -286
  87. package/scripts/workspace-evolver.js +2 -62
  88. package/src/api/server.js +290 -2578
  89. package/.claude/commands/dashboard.md +0 -15
  90. package/.claude/commands/thumbgate-blocked.md +0 -27
  91. package/.claude/commands/thumbgate-dashboard.md +0 -15
  92. package/.claude/commands/thumbgate-doctor.md +0 -30
  93. package/.claude/commands/thumbgate-guard.md +0 -36
  94. package/.claude/commands/thumbgate-protect.md +0 -30
  95. package/.claude/commands/thumbgate-rules.md +0 -30
  96. package/.well-known/agentic-verify.txt +0 -1
  97. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  98. package/adapters/letta/README.md +0 -41
  99. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  100. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  101. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  102. package/bench/observability-eval-suite.json +0 -26
  103. package/bin/dashboard-cli.js +0 -7
  104. package/commands/dashboard.md +0 -15
  105. package/commands/thumbgate-dashboard.md +0 -15
  106. package/config/builtin-lessons.json +0 -23
  107. package/config/gate-classifier-routing.json +0 -98
  108. package/config/post-deploy-marketing-pages.json +0 -61
  109. package/public/about.html +0 -162
  110. package/public/agent-manager.html +0 -179
  111. package/public/agents-cost-savings.html +0 -153
  112. package/public/ai-malpractice-prevention.html +0 -818
  113. package/public/assets/brand/github-social-preview.png +0 -0
  114. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  115. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  116. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  117. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  118. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  119. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  120. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  121. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  122. package/public/assets/brand/thumbgate-mark.svg +0 -21
  123. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  124. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  125. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  126. package/public/assets/legal-intake-control-flow.svg +0 -66
  127. package/public/brand/thumbgate-mark.svg +0 -19
  128. package/public/brand/thumbgate-og.svg +0 -16
  129. package/public/chatgpt-app.html +0 -330
  130. package/public/codex-enterprise.html +0 -123
  131. package/public/js/buyer-intent.js +0 -672
  132. package/public/pricing.html +0 -399
  133. package/scripts/action-receipts.js +0 -324
  134. package/scripts/activation-quickstart.js +0 -187
  135. package/scripts/agent-operations-planner.js +0 -621
  136. package/scripts/ai-component-inventory.js +0 -367
  137. package/scripts/async-eval-observability.js +0 -236
  138. package/scripts/audit.js +0 -65
  139. package/scripts/aws-blocks-guardrails.js +0 -228
  140. package/scripts/classifier-routing.js +0 -130
  141. package/scripts/dashboard-chat.js +0 -332
  142. package/scripts/feedback-aggregate.js +0 -281
  143. package/scripts/feedback-sanitizer.js +0 -105
  144. package/scripts/hob-pack.js +0 -591
  145. package/scripts/hook-stop-anti-claim.js +0 -301
  146. package/scripts/install-shim.js +0 -87
  147. package/scripts/mcp-oauth.js +0 -293
  148. package/scripts/noop-detect.js +0 -285
  149. package/scripts/omlx-smoke.js +0 -192
  150. package/scripts/parallel-workflow-orchestrator.js +0 -293
  151. package/scripts/plan-gate.js +0 -243
  152. package/scripts/plausible-domain-config.js +0 -99
  153. package/scripts/qa-scenario-planner.js +0 -136
  154. package/scripts/repeat-metric.js +0 -137
  155. package/scripts/secret-fixture-tokens.js +0 -61
  156. package/scripts/secret-redaction.js +0 -166
  157. package/scripts/self-harness-optimizer.js +0 -141
  158. package/scripts/self-healing-check.js +0 -193
  159. package/scripts/silent-failure-cluster.js +0 -531
  160. package/scripts/statusline-cache-read.js +0 -57
  161. package/scripts/sync-telemetry-from-prod.js +0 -374
  162. package/scripts/tool-contract-validator.js +0 -76
  163. package/scripts/trajectory-scorer.js +0 -63
  164. package/scripts/verify-marketing-pages-deployed.js +0 -195
  165. package/scripts/visitor-journey.js +0 -172
@@ -1,15 +0,0 @@
1
- ---
2
- name: dashboard
3
- description: Open the local HTTP dashboard for the current project in your web browser.
4
- ---
5
-
6
- # Open Dashboard
7
-
8
- Open the local HTTP dashboard for the current project in your web browser.
9
-
10
- ## Instructions
11
- Execute the following command in the project directory to open the browser dashboard scoped to the current repository:
12
- ```bash
13
- thumbgate-dashboard
14
- ```
15
-
@@ -1,27 +0,0 @@
1
- ---
2
- name: thumbgate-blocked
3
- description: Show what ThumbGate has actually blocked — gate enforcement stats and the full enforcement matrix. Use for "what has ThumbGate blocked", "show gate stats", "is enforcement working", "how many tokens did we save", "enforcement matrix".
4
- allowed-tools: mcp__thumbgate__gate_stats, mcp__thumbgate__enforcement_matrix, Bash(npx thumbgate gate-stats:*)
5
- ---
6
-
7
- # ThumbGate Blocked
8
-
9
- Show the enforcement record: how many risky actions were blocked vs warned, which gates fire most, and the full feedback → check → rejection pipeline.
10
-
11
- This command wraps existing ThumbGate capability — **no new logic**. It reads the live enforcement counters.
12
-
13
- ## Steps
14
-
15
- 1. Call the `gate_stats` MCP tool for the headline numbers: blocked count, warned count, and the top gates by hits. (CLI fallback: `npx thumbgate gate-stats`.)
16
- 2. Call the `enforcement_matrix` MCP tool for the full picture: feedback pipeline stats, active pre-action checks, and the rejection ledger with revival conditions.
17
- 3. Summarize for the user:
18
- - Total blocks (each block = a repeat mistake stopped before it spent tokens or did damage).
19
- - Most-triggered gates.
20
- - Anything in the rejection ledger that is close to revival.
21
- 4. If counts are all zero, note that enforcement is wired but hasn't fired yet, and point to `/thumbgate-guard` to promote a rule.
22
-
23
- ## Example
24
-
25
- ```
26
- /thumbgate-blocked
27
- ```
@@ -1,15 +0,0 @@
1
- ---
2
- name: thumbgate-dashboard
3
- description: Open the local HTTP dashboard for the current project in your web browser.
4
- ---
5
-
6
- # Open Scoped ThumbGate Dashboard
7
-
8
- Open the local HTTP dashboard for the current project in your web browser.
9
-
10
- ## Instructions
11
- Execute the following command in the project directory to open the browser dashboard:
12
- ```bash
13
- thumbgate-dashboard
14
- ```
15
-
@@ -1,30 +0,0 @@
1
- ---
2
- name: thumbgate-doctor
3
- description: Health-check the ThumbGate wiring for this project — hooks, MCP server, and agent-readiness — and report what's broken. Use for "is ThumbGate wired up", "thumbgate doctor", "check my guardrails are installed", "why aren't my gates firing", "agent readiness".
4
- allowed-tools: Bash(npx thumbgate doctor:*), mcp__thumbgate__check_operational_integrity
5
- ---
6
-
7
- # ThumbGate Doctor
8
-
9
- Audit whether ThumbGate is actually wired into this agent: PreToolUse / SessionStart hooks installed, MCP server reachable, lesson store present, and overall agent-readiness — then tell the user exactly what to fix.
10
-
11
- This command wraps existing ThumbGate capability — **no new logic**. It runs the existing doctor + integrity checks.
12
-
13
- ## Steps
14
-
15
- 1. Run the existing wiring/health audit:
16
- ```bash
17
- npx thumbgate doctor
18
- ```
19
- (Add `--json` for a machine-readable report.) It exits non-zero when the project is not `ready`.
20
- 2. For deeper runtime state, call the `check_operational_integrity` MCP tool to verify the server-side enforcement path is live, not just the local config.
21
- 3. Summarize:
22
- - ✅ what's wired (hooks, MCP, store, statusline).
23
- - ❌ what's missing, with the exact fix command (usually `npx thumbgate init`).
24
- 4. If everything is green, say so plainly with the readiness status; if not, lead with the single highest-impact fix.
25
-
26
- ## Example
27
-
28
- ```
29
- /thumbgate-doctor
30
- ```
@@ -1,36 +0,0 @@
1
- ---
2
- name: thumbgate-guard
3
- description: Turn the last agent mistake into a hard prevention rule the agent cannot bypass. Use after a bad tool call, a wrong action, or a thumbs-down — "guard against this", "block this from happening again", "never do that again", "promote this to a rule".
4
- allowed-tools: mcp__thumbgate__capture_feedback, Bash(npx thumbgate force-gate:*), Bash(npx thumbgate quickstart:*)
5
- ---
6
-
7
- # ThumbGate Guard
8
-
9
- Capture the mistake the agent just made and promote it into a Pre-Action Check (a `block` gate) so the same tool-call shape is intercepted before it runs again — in this and every future session, across Claude Code, Cursor, Codex, Gemini, Amp, and Cline.
10
-
11
- This command wraps existing ThumbGate capability. It adds **no new logic** — it routes to the real capture + force-promote path.
12
-
13
- ## Steps
14
-
15
- 1. Identify the specific bad action from the recent conversation (e.g. `git push --force origin main`, `DROP TABLE users`, deploy without tests). State it in one sentence.
16
- 2. Record the signal with the `capture_feedback` MCP tool:
17
- - `signal: "down"`
18
- - `context`: one sentence describing what went wrong
19
- - `whatWentWrong`: the concrete failure
20
- - `whatToChange`: the prevention action
21
- - `tags`: the domain (e.g. `git`, `database`, `deploy`)
22
- - If the user only gave a vague signal, pass the recent turns through `conversationWindow` / `chatHistory` for history-aware distillation instead of refusing.
23
- 3. Promote it to an enforced block gate using the existing force-promote path:
24
- ```bash
25
- npx thumbgate force-gate "<one-sentence context of the mistake>"
26
- ```
27
- This prints the new `gateId` and the total active gate count.
28
- 4. Show the user the promoted rule and confirm it is now enforced as a PreToolUse block.
29
-
30
- > First rule of the project and want the guided walkthrough (capture → promote → watch it block once)? Run `npx thumbgate quickstart` instead.
31
-
32
- ## Example
33
-
34
- ```
35
- /thumbgate-guard the agent force-pushed to main and overwrote a teammate's commit
36
- ```
@@ -1,30 +0,0 @@
1
- ---
2
- name: thumbgate-protect
3
- description: Show this repo's branch/release governance and grant a scoped, time-limited approval for an action that touches protected files. Use for "protect this branch", "is main protected", "approve this protected change", "branch governance", "let me edit a protected file".
4
- allowed-tools: mcp__thumbgate__get_branch_governance, mcp__thumbgate__approve_protected_action
5
- ---
6
-
7
- # ThumbGate Protect
8
-
9
- Inspect the protected-action posture for this project and, when the user explicitly approves, grant a scoped, expiring exception so a protected-file edit or publish can proceed under audit.
10
-
11
- This command wraps existing ThumbGate capability — **no new logic**. It reads governance state and records a time-boxed approval.
12
-
13
- ## Steps
14
-
15
- 1. Read the current posture with the `get_branch_governance` MCP tool: which branches are protected, release rules, and the protected-file globs in effect.
16
- 2. Report it plainly: what is protected, and what the agent is currently blocked from touching without approval.
17
- 3. **Only if the user explicitly asks to proceed**, grant a scoped approval with `approve_protected_action`:
18
- - `pathGlobs`: the smallest set of protected globs the action needs.
19
- - `reason`: why this is approved (one sentence).
20
- - `evidence`: supporting note (tests passing, owner sign-off, etc.) when available.
21
- - `ttlMs`: keep it short — default is 1 hour, never exceed what the task needs.
22
- 4. Confirm the approval id, covered globs, and expiry. Approvals are deliberately temporary and audited; re-run for the next task.
23
-
24
- > This is for granting *narrow, temporary* exceptions, not for disabling protection. Never use it to bypass branch governance wholesale.
25
-
26
- ## Example
27
-
28
- ```
29
- /thumbgate-protect
30
- ```
@@ -1,30 +0,0 @@
1
- ---
2
- name: thumbgate-rules
3
- description: List the active prevention rules and learned lessons guarding this project. Use to answer "what is ThumbGate protecting me from", "show my gates/rules", "what has the agent learned", "what's blocked here".
4
- allowed-tools: mcp__thumbgate__prevention_rules, mcp__thumbgate__get_reliability_rules, mcp__thumbgate__search_lessons, Bash(npx thumbgate rules:*)
5
- ---
6
-
7
- # ThumbGate Rules
8
-
9
- Show the guardrails currently in force for this project: the auto-promoted prevention rules, the reliability rules, and the promoted lessons behind them.
10
-
11
- This command wraps existing ThumbGate capability — **no new logic**. It reads the live rule + lesson stores.
12
-
13
- ## Steps
14
-
15
- 1. List the active prevention rules with the `prevention_rules` MCP tool (or the CLI fallback `npx thumbgate rules`).
16
- 2. Pull the reliability rules with `get_reliability_rules` to show which tool-call shapes are gated.
17
- 3. For each rule, surface the lesson it came from with `search_lessons` so the user sees *why* the rule exists, not just *what* it blocks.
18
- 4. Present a compact table:
19
-
20
- | Rule / Gate | Blocks | From lesson | State |
21
- |-------------|--------|-------------|-------|
22
- | … | … | … | active / archived |
23
-
24
- 5. If there are zero active rules, point the user to `/thumbgate-guard` to promote their first one.
25
-
26
- ## Example
27
-
28
- ```
29
- /thumbgate-rules
30
- ```
@@ -1 +0,0 @@
1
- 3588d0ad8f7b89fd7b9fbf771c1dd7dd09310e88aa6a7ae049b1decfa971650b
@@ -1,295 +0,0 @@
1
- 'use strict';
2
-
3
- // adapters/gcp/dfcx-webhook-gate.js
4
- // -----------------------------------------------------------------------------
5
- // ThumbGate Enterprise — Dialogflow CX fulfillment webhook guardrail.
6
- //
7
- // Routes a Dialogflow CX (DFCX) fulfillment request through ThumbGate's
8
- // pre-action gate engine BEFORE the real fulfillment side-effect runs (DB/CRM/
9
- // billing write). If a configured policy gate denies the action, or the action
10
- // is a known same-session repeat, the side-effect is blocked and a safe DFCX
11
- // WebhookResponse is returned instead of executing it.
12
- //
13
- // Design: ThumbGate is a *guard in front of* the customer's existing fulfillment
14
- // function — it decides whether that function is allowed to run. It does not
15
- // replace it, mutate Playbooks, or call any Google API itself.
16
- //
17
- // This enterprise adapter is also listed in package.json "files" because
18
- // src/api/server.js loads it for the local enterprise Dialogflow dashboard routes.
19
- // The same module can still be deployed as Cloud Run / Cloud Functions middleware.
20
- // -----------------------------------------------------------------------------
21
-
22
- const path = require('path');
23
-
24
- const REPO_ROOT = path.join(__dirname, '..', '..');
25
- const gates = require(path.join(REPO_ROOT, 'scripts', 'gates-engine'));
26
-
27
- // Risk scorer is optional: it needs a trained model on disk. Degrade to null.
28
- let riskScorer = null;
29
- try {
30
- riskScorer = require(path.join(REPO_ROOT, 'scripts', 'risk-scorer'));
31
- } catch (_) {
32
- riskScorer = null;
33
- }
34
-
35
- // Deterministic stringify so the same parameter object always yields the same
36
- // action id (used for same-session repeat detection).
37
- function stableStringify(value) {
38
- if (value === null || typeof value !== 'object') return JSON.stringify(value);
39
- if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';
40
- const keys = Object.keys(value).sort();
41
- return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(value[k])).join(',') + '}';
42
- }
43
-
44
- // Map a DFCX WebhookRequest into a ThumbGate (toolName, toolInput) action.
45
- // DFCX fulfillment tag -> toolName ("dfcx:<tag>"); session parameters -> toolInput.
46
- // Supports both camelCase (standard DFCX) and snake_case (legacy/internal) formatting.
47
- function mapDfcxToAction(reqBody) {
48
- const body = reqBody || {};
49
- const fulfillmentInfo = body.fulfillmentInfo || body.fulfillment_info || {};
50
- const tag = fulfillmentInfo.tag || 'unknown';
51
- const sessionInfo = body.sessionInfo || body.session_info || {};
52
- const params = sessionInfo.parameters || {};
53
- const sessionId = sessionInfo.session || '';
54
- return {
55
- tag,
56
- toolName: 'dfcx:' + tag,
57
- toolInput: params,
58
- sessionId,
59
- };
60
- }
61
-
62
- // A DFCX webhook is fully untrusted (internet-facing), unlike a local coding
63
- // agent. These allowlists reject anything that could carry shell/path
64
- // metacharacters before the action ever reaches the gate engine.
65
- const SAFE_TOKEN = /^[A-Za-z0-9._\s-]{1,64}$/; // fulfillment tags, parameter names
66
- const SAFE_VALUE = /^[^`\;|&<>]{0,2048}$/; // parameter string values (allow standard punctuation, block command injection)
67
-
68
- // Evaluate whether a DFCX fulfillment should be allowed to execute.
69
- // Returns { allowed, decision, gate, message, severity, repeat, risk, action }.
70
- function evaluateDfcxFulfillment(reqBody, opts = {}) {
71
- const raw = mapDfcxToAction(reqBody);
72
-
73
- // 0) Validate the untrusted webhook input and rebuild a SAFE action inline,
74
- // before any value reaches the gate engine. Block on any unsafe token/value
75
- // so attacker-controlled input cannot reach a path/command sink downstream.
76
- const blockedUnsafe = (reason) => ({
77
- allowed: false,
78
- decision: 'deny',
79
- gate: 'dfcx-unsafe-input',
80
- message: 'The request contained unsafe input and was blocked.',
81
- severity: 'critical',
82
- repeat: false,
83
- risk: null,
84
- action: { tag: String(raw.tag), toolName: 'dfcx:unsafe', toolInput: {}, sessionId: raw.sessionId },
85
- reason,
86
- });
87
- const tag = String(raw.tag);
88
- if (!SAFE_TOKEN.test(tag)) return blockedUnsafe('unsafe fulfillment tag');
89
- const toolName = 'dfcx:' + tag;
90
- const toolInput = {};
91
- const rawParams = raw.toolInput && typeof raw.toolInput === 'object' ? raw.toolInput : {};
92
- for (const key of Object.keys(rawParams)) {
93
- if (!SAFE_TOKEN.test(key)) return blockedUnsafe('unsafe parameter name');
94
- const value = rawParams[key];
95
- if (typeof value === 'string') {
96
- if (!SAFE_VALUE.test(value)) return blockedUnsafe('unsafe parameter value');
97
- toolInput[key] = value;
98
- } else if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
99
- toolInput[key] = value;
100
- }
101
- // non-scalar values are intentionally dropped (never forwarded downstream).
102
- }
103
- const action = { tag, toolName, toolInput, sessionId: raw.sessionId };
104
-
105
- // 1) Configured policy gates. The pilot configures DFCX-relevant gates (e.g.
106
- // "block dfcx:process-refund when amount > limit and not approved"). With no
107
- // custom config this is simply a no-op (allow).
108
- let gateResult = null;
109
- try {
110
- gateResult = gates.evaluateGates(toolName, toolInput, opts.configPath);
111
- } catch (_) {
112
- gateResult = null;
113
- }
114
- const denied = Boolean(gateResult && gateResult.decision === 'deny');
115
-
116
- // 2) Same-session repeat detection — works with zero custom config.
117
- const actionId = action.toolName + ':' + stableStringify(action.toolInput);
118
- let repeat = false;
119
- if (typeof gates.hasAction === 'function') {
120
- try { repeat = Boolean(gates.hasAction(actionId)); } catch (_) { repeat = false; }
121
- }
122
- if (typeof gates.trackAction === 'function') {
123
- try { gates.trackAction(actionId, { source: 'dfcx', tag: action.tag }); } catch (_) { /* non-fatal */ }
124
- }
125
-
126
- // 3) Optional risk score (best-effort; null when no model is trained).
127
- let risk = null;
128
- if (riskScorer && typeof riskScorer.predictRisk === 'function') {
129
- try {
130
- const candidate = typeof riskScorer.buildRiskCandidate === 'function'
131
- ? riskScorer.buildRiskCandidate({ toolName: action.toolName, toolInput: action.toolInput })
132
- : { toolName: action.toolName, toolInput: action.toolInput };
133
- const r = riskScorer.predictRisk(candidate);
134
- risk = typeof r === 'number' ? r : (r && typeof r.risk === 'number' ? r.risk : null);
135
- } catch (_) {
136
- risk = null;
137
- }
138
- }
139
-
140
- const blockOnRepeat = opts.blockOnRepeat !== false && repeat;
141
- const allowed = !denied && !blockOnRepeat;
142
-
143
- return {
144
- allowed,
145
- decision: allowed ? 'allow' : 'deny',
146
- gate: denied ? gateResult.gate : (blockOnRepeat ? 'dfcx-repeat-action' : null),
147
- message: denied
148
- ? gateResult.message
149
- : (blockOnRepeat ? 'This action was already attempted in this session and is blocked as a repeat.' : null),
150
- severity: denied ? gateResult.severity : (blockOnRepeat ? 'high' : null),
151
- repeat,
152
- risk,
153
- action,
154
- };
155
- }
156
-
157
- // Build a DFCX WebhookResponse that safely halts the turn without side-effects.
158
- // Supports both camelCase (standard DFCX) and snake_case (legacy/internal) formatting.
159
- function buildBlockResponse(evaluation, opts = {}) {
160
- const message = opts.blockedMessage
161
- || 'This request was held by a safety policy and was not completed. A team member may follow up.';
162
- const payload = {
163
- fulfillment_response: { messages: [{ text: { text: [message] } }] },
164
- fulfillmentResponse: { messages: [{ text: { text: [message] } }] },
165
- session_info: {
166
- parameters: {
167
- thumbgate_blocked: true,
168
- thumbgate_gate: evaluation.gate || null,
169
- thumbgate_severity: evaluation.severity || null,
170
- },
171
- },
172
- sessionInfo: {
173
- parameters: {
174
- thumbgate_blocked: true,
175
- thumbgate_gate: evaluation.gate || null,
176
- thumbgate_severity: evaluation.severity || null,
177
- },
178
- },
179
- };
180
- return payload;
181
- }
182
-
183
- // Annotate an allowed (passed-through) response so downstream flows can observe
184
- // that ThumbGate evaluated and permitted the turn. Never throws on odd shapes.
185
- // Populates both camelCase and snake_case variants to ensure compatibility.
186
- function annotateAllowed(response, evaluation) {
187
- const base = response && typeof response === 'object' ? response : {};
188
-
189
- const sessionInfo = base.sessionInfo || base.session_info || {};
190
- const params = sessionInfo.parameters && typeof sessionInfo.parameters === 'object' ? sessionInfo.parameters : {};
191
-
192
- const updatedParams = Object.assign({}, params, {
193
- thumbgate_blocked: false,
194
- thumbgate_risk: evaluation.risk,
195
- });
196
-
197
- const updatedSessionInfo = Object.assign({}, sessionInfo, {
198
- parameters: updatedParams,
199
- });
200
-
201
- const updated = Object.assign({}, base, {
202
- session_info: updatedSessionInfo,
203
- sessionInfo: updatedSessionInfo,
204
- });
205
-
206
- if (base.fulfillment_response) {
207
- updated.fulfillmentResponse = base.fulfillment_response;
208
- } else if (base.fulfillmentResponse) {
209
- updated.fulfillment_response = base.fulfillmentResponse;
210
- }
211
-
212
- return updated;
213
- }
214
-
215
- // Guard a DFCX webhook: run the gate; only invoke the real fulfillment when
216
- // allowed. `fulfill(reqBody) -> WebhookResponse` is the customer's existing
217
- // fulfillment function. Returns { blocked, response, evaluation }.
218
- async function guardDfcxWebhook(reqBody, fulfill, opts = {}) {
219
- const evaluation = evaluateDfcxFulfillment(reqBody, opts);
220
- if (!evaluation.allowed) {
221
- return { blocked: true, response: buildBlockResponse(evaluation, opts), evaluation };
222
- }
223
- const fulfilled = typeof fulfill === 'function'
224
- ? await fulfill(reqBody)
225
- : { fulfillment_response: { messages: [] } };
226
- return { blocked: false, response: annotateAllowed(fulfilled, evaluation), evaluation };
227
- }
228
-
229
- // Reject bodies larger than this. A DFCX WebhookRequest is small (a few KB); an
230
- // unbounded reader on an internet-facing endpoint is a memory-exhaustion vector.
231
- const MAX_BODY_BYTES = 1024 * 1024; // 1 MiB
232
-
233
- // Read a JSON request body from a Node IncomingMessage stream, with a hard size
234
- // cap so a malicious/misconfigured caller cannot exhaust memory.
235
- function readJsonBody(req) {
236
- return new Promise((resolve, reject) => {
237
- let raw = '';
238
- let bytes = 0;
239
- let aborted = false;
240
- req.on('data', (chunk) => {
241
- if (aborted) return;
242
- bytes += chunk.length;
243
- if (bytes > MAX_BODY_BYTES) {
244
- aborted = true;
245
- const err = new Error('request body exceeds ' + MAX_BODY_BYTES + ' bytes');
246
- err.statusCode = 413;
247
- try { req.destroy(); } catch (_) { /* ignore */ }
248
- return reject(err);
249
- }
250
- raw += chunk;
251
- });
252
- req.on('end', () => {
253
- if (aborted) return;
254
- if (!raw) return resolve({});
255
- try { resolve(JSON.parse(raw)); } catch (e) { reject(e); }
256
- });
257
- req.on('error', reject);
258
- });
259
- }
260
-
261
- // Plain Node HTTP handler for Cloud Run / Cloud Functions (no framework dep).
262
- function createHttpHandler(fulfill, opts = {}) {
263
- return async function handler(req, res) {
264
- try {
265
- const body = req && req.body && typeof req.body === 'object'
266
- ? req.body
267
- : await readJsonBody(req);
268
- const { response, evaluation } = await guardDfcxWebhook(body, fulfill, opts);
269
- if (typeof opts.onDecision === 'function') {
270
- try { opts.onDecision(evaluation); } catch (_) { /* observability must not break the turn */ }
271
- }
272
- res.statusCode = 200;
273
- res.setHeader('content-type', 'application/json');
274
- res.end(JSON.stringify(response));
275
- } catch (err) {
276
- // Log internally for operators; never leak error/stack details to the
277
- // external caller (Dialogflow CX / the open internet).
278
- try { console.error('thumbgate-dfcx-gate error:', err); } catch (_) { /* ignore */ }
279
- res.statusCode = 500;
280
- res.setHeader('content-type', 'application/json');
281
- res.end(JSON.stringify({ error: 'thumbgate-dfcx-gate-failure' }));
282
- }
283
- };
284
- }
285
-
286
- module.exports = {
287
- mapDfcxToAction,
288
- evaluateDfcxFulfillment,
289
- buildBlockResponse,
290
- annotateAllowed,
291
- guardDfcxWebhook,
292
- createHttpHandler,
293
- // exposed for tests
294
- stableStringify,
295
- };
@@ -1,41 +0,0 @@
1
- # Letta Adapter
2
-
3
- Letta is a stateful-agent runtime with persistent memory, MCP tools, custom server tools, and client-side tool execution. ThumbGate should not compete with that memory surface. ThumbGate should sit underneath it as the deterministic local enforcement layer.
4
-
5
- ## Integration Pattern
6
-
7
- Use `thumbgate-letta-adapter.js` to wrap Letta tool execution:
8
-
9
- 1. Letta agent proposes a tool call.
10
- 2. The wrapper normalizes the Letta event into ThumbGate's provider-action schema.
11
- 3. Your app calls ThumbGate's local gate-check transport.
12
- 4. If ThumbGate blocks or requires approval, the tool executor is never called.
13
- 5. If allowed, the original Letta tool executes normally.
14
-
15
- This works for the two practical Letta surfaces:
16
-
17
- - **MCP tools**: Letta forwards the tool call through the Letta server to an MCP server. Wrap the forwarding boundary or expose the downstream tool through a ThumbGate-guarded MCP server.
18
- - **Client tools**: Letta asks your client application to execute a tool locally. Wrap the client tool handler with `createLettaToolGuard`.
19
-
20
- ## Example
21
-
22
- ```js
23
- const { createLettaToolGuard } = require('./thumbgate-letta-adapter');
24
-
25
- const guardedTool = createLettaToolGuard({
26
- gateCheck: async (normalizedAction) => thumbgateGateCheck(normalizedAction),
27
- executeTool: async (lettaEvent) => runOriginalTool(lettaEvent),
28
- });
29
-
30
- await guardedTool({
31
- agentId: 'agent-123',
32
- clientTool: {
33
- name: 'shell',
34
- input: { command: 'git push --force origin main' },
35
- },
36
- });
37
- ```
38
-
39
- ## Positioning
40
-
41
- Letta helps agents remember and operate with state. ThumbGate helps teams decide what those agents are allowed to do next. The moat is adapter coverage: ThumbGate works under memory-first runtimes instead of asking buyers to replace them.
@@ -1,133 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const { normalizeProviderAction } = require('../../scripts/provider-action-normalizer');
5
-
6
- function asObject(value) {
7
- return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
8
- }
9
-
10
- function firstString(...values) {
11
- for (const value of values) {
12
- const text = String(value || '').trim();
13
- if (text) return text;
14
- }
15
- return '';
16
- }
17
-
18
- function extractLettaToolCall(input = {}) {
19
- const event = asObject(input);
20
- const toolCall = asObject(event.toolCall || event.tool_call || event.lettaToolCall);
21
- const functionCall = asObject(toolCall.function || event.function);
22
- const mcp = asObject(event.mcp || event.mcpToolCall);
23
- const clientTool = asObject(event.clientTool || event.client_tool);
24
- const params = asObject(event.params);
25
-
26
- const name = firstString(
27
- event.toolName,
28
- event.name,
29
- toolCall.name,
30
- functionCall.name,
31
- clientTool.name,
32
- mcp.name,
33
- params.name
34
- );
35
- const args = asObject(
36
- event.arguments
37
- || event.args
38
- || event.input
39
- || toolCall.arguments
40
- || toolCall.input
41
- || functionCall.arguments
42
- || clientTool.arguments
43
- || clientTool.input
44
- || mcp.arguments
45
- || params.arguments
46
- );
47
-
48
- return {
49
- id: firstString(event.id, event.toolCallId, event.tool_call_id, toolCall.id, clientTool.id),
50
- name,
51
- arguments: args,
52
- server: firstString(event.mcpServer, mcp.server, params.server),
53
- surface: firstString(event.surface, event.executionSurface, clientTool.name ? 'client-tool' : mcp.name || params.name ? 'mcp-tool' : 'server-tool'),
54
- };
55
- }
56
-
57
- function normalizeLettaAction(input = {}) {
58
- const event = asObject(input);
59
- const toolCall = extractLettaToolCall(event);
60
-
61
- const normalized = normalizeProviderAction({
62
- ...event,
63
- provider: 'letta',
64
- toolCall: {
65
- id: toolCall.id,
66
- name: toolCall.name,
67
- input: toolCall.arguments,
68
- },
69
- toolName: toolCall.name,
70
- input: toolCall.arguments,
71
- mcpServer: toolCall.server,
72
- });
73
-
74
- return {
75
- ...normalized,
76
- provider: 'letta',
77
- agentRuntime: 'letta',
78
- letta: {
79
- agentId: firstString(event.agentId, event.agent_id),
80
- messageId: firstString(event.messageId, event.message_id),
81
- surface: toolCall.surface,
82
- toolCallId: toolCall.id,
83
- },
84
- };
85
- }
86
-
87
- function normalizeGateDecision(decision = {}) {
88
- const value = asObject(decision);
89
- const raw = firstString(value.decision, value.mode, value.action, value.status).toLowerCase();
90
- const blocked = value.allowed === false
91
- || value.accepted === false
92
- || value.blocked === true
93
- || ['block', 'deny', 'denied', 'reject', 'rejected'].includes(raw);
94
- const approvalRequired = value.requiresApproval === true || raw === 'approve';
95
- return {
96
- allowed: !blocked && !approvalRequired,
97
- blocked,
98
- approvalRequired,
99
- reason: firstString(value.reason, value.message, Array.isArray(value.reasons) ? value.reasons.join('; ') : ''),
100
- raw: value,
101
- };
102
- }
103
-
104
- function createLettaToolGuard({ gateCheck, executeTool, onDecision } = {}) {
105
- if (typeof gateCheck !== 'function') {
106
- throw new TypeError('createLettaToolGuard requires a gateCheck function');
107
- }
108
- if (typeof executeTool !== 'function') {
109
- throw new TypeError('createLettaToolGuard requires an executeTool function');
110
- }
111
-
112
- return async function guardedLettaTool(input = {}) {
113
- const normalizedAction = normalizeLettaAction(input);
114
- const decision = normalizeGateDecision(await gateCheck(normalizedAction));
115
- if (typeof onDecision === 'function') {
116
- await onDecision({ normalizedAction, decision });
117
- }
118
- if (!decision.allowed) {
119
- const error = new Error(decision.reason || 'ThumbGate blocked this Letta tool call before execution.');
120
- error.code = decision.approvalRequired ? 'THUMBGATE_APPROVAL_REQUIRED' : 'THUMBGATE_BLOCKED';
121
- error.thumbgate = { normalizedAction, decision };
122
- throw error;
123
- }
124
- return executeTool(input, { normalizedAction, decision });
125
- };
126
- }
127
-
128
- module.exports = {
129
- createLettaToolGuard,
130
- extractLettaToolCall,
131
- normalizeGateDecision,
132
- normalizeLettaAction,
133
- };