thumbgate 1.26.8 → 1.27.2

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 (50) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/agentic-verify.txt +1 -0
  4. package/.well-known/llms.txt +2 -0
  5. package/.well-known/mcp/server-card.json +1 -1
  6. package/README.md +20 -9
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gcp/dfcx-webhook-gate.js +295 -0
  9. package/adapters/mcp/server-stdio.js +28 -1
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +132 -7
  13. package/bin/dashboard-cli.js +7 -0
  14. package/config/gate-classifier-routing.json +98 -0
  15. package/config/gate-templates.json +60 -0
  16. package/config/mcp-allowlists.json +8 -7
  17. package/config/model-candidates.json +71 -6
  18. package/package.json +26 -10
  19. package/public/chatgpt-app.html +330 -0
  20. package/public/codex-plugin.html +66 -14
  21. package/public/dashboard.html +203 -17
  22. package/public/index.html +79 -4
  23. package/public/learn.html +70 -0
  24. package/public/lessons.html +129 -6
  25. package/public/numbers.html +2 -2
  26. package/public/pricing.html +20 -2
  27. package/scripts/agent-operations-planner.js +621 -0
  28. package/scripts/agent-reward-model.js +53 -1
  29. package/scripts/ai-component-inventory.js +367 -0
  30. package/scripts/classifier-routing.js +130 -0
  31. package/scripts/cli-schema.js +26 -0
  32. package/scripts/dashboard-chat.js +64 -17
  33. package/scripts/feedback-sanitizer.js +105 -0
  34. package/scripts/gates-engine.js +258 -61
  35. package/scripts/hybrid-feedback-context.js +141 -7
  36. package/scripts/memory-scope-readiness.js +159 -0
  37. package/scripts/parallel-workflow-orchestrator.js +293 -0
  38. package/scripts/plausible-domain-config.js +86 -0
  39. package/scripts/plausible-server-events.js +4 -2
  40. package/scripts/proxy-pointer-rag-guardrails.js +42 -1
  41. package/scripts/qa-scenario-planner.js +136 -0
  42. package/scripts/repeat-metric.js +28 -12
  43. package/scripts/secret-fixture-tokens.js +61 -0
  44. package/scripts/secret-scanner.js +44 -5
  45. package/scripts/security-scanner.js +80 -0
  46. package/scripts/seo-gsd.js +53 -0
  47. package/scripts/thumbgate-bench.js +16 -1
  48. package/scripts/tool-registry.js +37 -0
  49. package/scripts/workflow-sentinel.js +189 -4
  50. package/src/api/server.js +276 -10
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate-marketplace",
3
- "version": "1.26.8",
3
+ "version": "1.27.2",
4
4
  "owner": {
5
5
  "name": "Igor Ganapolsky",
6
6
  "email": "ig5973700@gmail.com"
@@ -14,7 +14,7 @@
14
14
  "source": "npm",
15
15
  "package": "thumbgate"
16
16
  },
17
- "version": "1.26.8",
17
+ "version": "1.27.2",
18
18
  "author": {
19
19
  "name": "Igor Ganapolsky",
20
20
  "email": "ig5973700@gmail.com",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbgate",
3
3
  "description": "One 👎 becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
4
- "version": "1.26.8",
4
+ "version": "1.27.2",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky",
7
7
  "email": "ig5973700@gmail.com",
@@ -0,0 +1 @@
1
+ 3588d0ad8f7b89fd7b9fbf771c1dd7dd09310e88aa6a7ae049b1decfa971650b
@@ -48,7 +48,9 @@ npx thumbgate init --agent claude-code
48
48
 
49
49
  ## Pricing
50
50
 
51
+ - ChatGPT App / GPT Action: https://thumbgate.ai/chatgpt-app
51
52
  - Free GPT: advice, checkpointing, and setup help in ChatGPT
53
+ - Codex Plugin: https://thumbgate.ai/codex-plugin
52
54
  - Free local CLI: 5 captures/day, 25 total captures, up to 3 active auto-promoted prevention rules, and local Pre-Action Checks after install (recall and lesson search are Pro-only)
53
55
  - Pro: $19/mo or $149/yr — personal enforcement proof, local dashboard, check debugger, DPO export, synced lessons/rules across machines, and review-ready exports
54
56
  - Team: $49/seat/mo, 3-seat minimum after intake — shared lessons, org visibility, approval boundaries, and rollout proof
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.26.8",
3
+ "version": "1.27.2",
4
4
  "description": "ThumbGate — 👍👎 feedback that teaches your AI agent. Thumbs down a mistake, it never happens again.",
5
5
  "homepage": "https://thumbgate.ai",
6
6
  "transport": "stdio",
package/README.md CHANGED
@@ -6,9 +6,9 @@
6
6
  </a>
7
7
  </p>
8
8
 
9
- **AI agents repeat mistakes. In regulated industries, one wrong action is a liability event.**
9
+ **AI coding agents repeat mistakes and one wrong tool call can wipe a directory, leak a key, or push broken code.**
10
10
 
11
- ThumbGate is deterministic pre-action governance for AI agents. From developer workflows to legal intake to financial complianceone rule blocks unauthorized actions before they execute, across every session, every agent, every model.
11
+ ThumbGate is the local-first firewall for AI coding agents. It runs in the PreToolUse hook on your machine and blocks dangerous tool calls `rm -rf`, secret exfiltration, off-scope edits, a bad `git push` — before they execute, across Claude Code, Cursor, Codex, Gemini, Amp, Cline, and OpenCode. No server, no gateway. (Regulated-industry policy templates — legal intake, financial compliance, healthcare — build on the same engine.)
12
12
 
13
13
  The product is a self-improving enforcement layer: thumbs-down feedback, prompt evaluation, and proof from prior runs become prevention rules that permanently stop repeated failures before the next tool call.
14
14
 
@@ -24,7 +24,7 @@ The product is a self-improving enforcement layer: thumbs-down feedback, prompt
24
24
  npx thumbgate init # auto-detects your agent, wires hooks, 30 seconds
25
25
  ```
26
26
 
27
- Works with **Claude Code, Cursor, Codex, Gemini CLI, Amp, Cline, OpenCode** and any MCP-compatible agent. Free tier: unlimited feedback captures and 5 active auto-promoted prevention rules. [Pro: $19/mo or $149/yr](https://thumbgate.ai/checkout/pro?utm_source=github&utm_medium=readme) — unlimited rules, history-aware lessons, feedback sessions, dashboard, DPO export. Team is $49/seat/mo with a shared hosted lesson DB and org dashboard.
27
+ Works with **Claude Code, Cursor, Codex, Gemini CLI, Amp, Cline, OpenCode** and any MCP-compatible agent. Free tier: 5 feedback captures/day, 25 total captures, and 3 active auto-promoted prevention rules. [Pro: $19/mo or $149/yr](https://thumbgate.ai/checkout/pro?utm_source=github&utm_medium=readme) — unlimited feedback captures, unlimited rules, history-aware lessons, feedback sessions, dashboard, DPO export. Team is $49/seat/mo with a shared hosted lesson DB and org dashboard.
28
28
 
29
29
  [![CI](https://github.com/IgorGanapolsky/ThumbGate/actions/workflows/ci.yml/badge.svg)](https://github.com/IgorGanapolsky/ThumbGate/actions/workflows/ci.yml)
30
30
  [![npm](https://img.shields.io/npm/v/thumbgate)](https://www.npmjs.com/package/thumbgate)
@@ -255,6 +255,15 @@ Open the Codex plugin install page or download the standalone bundle from GitHub
255
255
  2. Direct zip: [thumbgate-codex-plugin.zip](https://github.com/IgorGanapolsky/ThumbGate/releases/latest/download/thumbgate-codex-plugin.zip)
256
256
  3. Follow: [plugins/codex-profile/INSTALL.md](plugins/codex-profile/INSTALL.md)
257
257
 
258
+ ### Install ChatGPT App / GPT Action
259
+
260
+ ChatGPT is the advice, checkpointing, and typed-feedback surface; ThumbGate's hard enforcement still runs locally in Codex, Claude Code, Cursor, Gemini CLI, Amp, OpenCode, MCP, or CI after install.
261
+
262
+ 1. App page: [thumbgate.ai/chatgpt-app](https://thumbgate.ai/chatgpt-app)
263
+ 2. Live GPT: [thumbgate.ai/go/gpt](https://thumbgate.ai/go/gpt?utm_source=github&utm_medium=readme&utm_campaign=chatgpt_app)
264
+ 3. GPT Action schema: [thumbgate.ai/openapi.yaml](https://thumbgate.ai/openapi.yaml)
265
+ 4. Follow: [adapters/chatgpt/INSTALL.md](adapters/chatgpt/INSTALL.md)
266
+
258
267
  ---
259
268
 
260
269
  ## How It Works
@@ -331,7 +340,8 @@ npx thumbgate explore # terminal explorer for lessons, checks, stats
331
340
  npx thumbgate background-governance # review background-agent run risk
332
341
  npx thumbgate model-candidates --workload=dashboard-analysis --provider=openai --json # evaluate GPT-5.5 routing
333
342
  npx thumbgate native-messaging-audit # inspect local browser bridges and extension hosts
334
- npx thumbgate dashboard # open local dashboard
343
+ npx thumbgate dashboard --open # open local project-scoped dashboard in browser
344
+ thumbgate-dashboard # standalone browser dashboard shortcut (run '/project:thumbgate-dashboard' in Claude/Grok)
335
345
  npx thumbgate serve # start MCP server on stdio
336
346
  npx thumbgate bench # run reliability benchmark
337
347
  npx thumbgate bench --programbench-smoke # include cleanroom whole-repo proof lane
@@ -372,8 +382,8 @@ If you change MCP or hook settings, restart the affected agent session so Claude
372
382
  | | Free | Pro ($19/mo) | Team ($49/seat/mo) | Enterprise |
373
383
  |---|---|---|---|---|
374
384
  | Local CLI + enforced checks | ✅ | ✅ | ✅ | ✅ |
375
- | Feedback captures (lifetime) | 3 | Unlimited | Unlimited | Unlimited |
376
- | Auto-promoted prevention rules | 1 | Unlimited | Unlimited | Unlimited |
385
+ | Feedback captures | 5/day, 25 total | Unlimited | Unlimited | Unlimited |
386
+ | Auto-promoted prevention rules | 3 active | Unlimited | Unlimited | Unlimited |
377
387
  | MCP agent integrations | All | All | All | All |
378
388
  | Personal dashboard | — | ✅ | ✅ | ✅ |
379
389
  | DPO export (model fine-tuning) | — | ✅ | ✅ | ✅ |
@@ -386,7 +396,7 @@ If you change MCP or hook settings, restart the affected agent session so Claude
386
396
  | Compliance audit export | — | — | — | ✅ |
387
397
  | Dedicated onboarding + SLA | — | — | — | ✅ |
388
398
 
389
- The free tier gives you unlimited feedback captures and up to 5 active auto-promoted prevention rules — generous enough to make ThumbGate part of your daily flow. MCP integrations for all agents (Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode) ship free.
399
+ The free tier gives you 5 feedback captures/day, 25 total captures, and up to 3 active auto-promoted prevention rules — enough to prove the loop before buying. MCP integrations for all agents (Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode) ship free.
390
400
 
391
401
  Pro ($19/mo or $149/yr) removes the rule cap and adds history-aware lesson recall, lesson search, DPO export, and a personal dashboard. Team ($49/seat/mo) adds a shared hosted lesson DB, org dashboard, and shared enforcement across the org. Enterprise adds regulatory gate templates (legal intake, financial compliance, healthcare), custom policy layers scoped to firm/practice-area, compliance audit export, and dedicated onboarding with SLA.
392
402
 
@@ -477,7 +487,7 @@ curl -X POST http://localhost:3456/v1/dpo/export \
477
487
  | Layer | Technology |
478
488
  |-------|-----------|
479
489
  | **Storage** | SQLite + FTS5, LanceDB vectors, JSONL logs |
480
- | **Capture** | Unlimited feedback captures (free + Pro) |
490
+ | **Capture** | 5/day, 25 total on Free; unlimited on Pro, Team, and Enterprise |
481
491
  | **Intelligence** | MemAlign dual recall, Thompson Sampling |
482
492
  | **Enforcement** | PreToolUse hook engine, Checks config |
483
493
  | **Interfaces** | MCP stdio, HTTP API, CLI (Node.js >=18) |
@@ -499,6 +509,7 @@ Every Changeset is tied to the exact `main` merge commit and generates Verificat
499
509
 
500
510
  ## Integrations
501
511
 
512
+ - **[ChatGPT App / GPT Action](https://thumbgate.ai/chatgpt-app)** — First-class ChatGPT distribution page with the live GPT, public OpenAPI Action schema, and local enforcement install path
502
513
  - **[Open ThumbGate GPT](https://thumbgate.ai/go/gpt?utm_source=github&utm_medium=readme&utm_campaign=readme_gpt)** — ThumbGate GPT: start here. Paste agent actions, get advice + checkpointing. No, users do not have to keep chatting inside the ThumbGate GPT to use ThumbGate — the hard enforcement layer still runs where the work happens.
503
514
  - **[Claude Desktop Extension](https://github.com/IgorGanapolsky/ThumbGate/releases/latest/download/thumbgate-claude-desktop.mcpb)** — One-click install for Claude Desktop
504
515
  - **[Codex Plugin](https://thumbgate.ai/codex-plugin)** — Auto-updating standalone bundle and install page for Codex CLI
@@ -562,7 +573,7 @@ Those are suggestions the agent can ignore. ThumbGate checks are enforced — th
562
573
  If it supports MCP or pre-action hooks, yes. Claude Code, Claude Desktop, Cursor, Codex, Gemini CLI, Amp, Cline, OpenCode all work out of the box.
563
574
 
564
575
  **Is it free?**
565
- The free tier gives you unlimited feedback captures and up to 5 active auto-promoted prevention rules — generous enough for solo devs to use daily. MCP integrations ship free for every agent.
576
+ The free tier gives you 5 feedback captures/day, 25 total captures, and up to 3 active auto-promoted prevention rules — enough for solo devs to prove a blocked repeat before upgrading. MCP integrations ship free for every agent.
566
577
 
567
578
  Pro ($19/mo or $149/yr) removes the rule cap and adds history-aware lesson recall, lesson search, and a personal dashboard. Team ($49/seat/mo) adds a shared hosted lesson DB, org dashboard, and shared enforcement.
568
579
 
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.26.8", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.27.2", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.26.8", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.27.2", "thumbgate", "gate-check"]
12
12
  }
13
13
  }
14
14
  }
@@ -0,0 +1,295 @@
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
+ };
@@ -231,7 +231,7 @@ const {
231
231
  finalizeSession: finalizeFeedbackSession,
232
232
  } = require('../../scripts/feedback-session');
233
233
 
234
- const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.26.8' };
234
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.2' };
235
235
  const COMMERCE_CATEGORIES = [
236
236
  'product_recommendation',
237
237
  'brand_compliance',
@@ -745,6 +745,23 @@ async function callToolInner(name, args) {
745
745
  },
746
746
  ));
747
747
  }
748
+ case 'ai_component_inventory': {
749
+ const {
750
+ scanAiComponents,
751
+ buildCycloneDxMlBom,
752
+ formatInventoryText,
753
+ } = require('../../scripts/ai-component-inventory');
754
+ const rootDir = args.rootDir ? path.resolve(String(args.rootDir)) : process.cwd();
755
+ const inventory = scanAiComponents({
756
+ rootDir,
757
+ maxFiles: args.maxFiles ? Number(args.maxFiles) : undefined,
758
+ includeSnippets: args.includeSnippets !== false,
759
+ });
760
+ const format = String(args.format || 'summary').toLowerCase();
761
+ if (format === 'cyclonedx') return toTextResult(buildCycloneDxMlBom(inventory));
762
+ if (format === 'json') return toTextResult(inventory);
763
+ return toTextResult(formatInventoryText(inventory));
764
+ }
748
765
  case 'search_thumbgate':
749
766
  enforceLimit('search_thumbgate');
750
767
  return toTextResult(searchThumbgate({
@@ -952,6 +969,7 @@ async function callToolInner(name, args) {
952
969
  summary: args.summary,
953
970
  allowedPaths: args.allowedPaths,
954
971
  protectedPaths: args.protectedPaths,
972
+ workflowContract: args.workflowContract,
955
973
  repoPath: args.repoPath,
956
974
  localOnly: args.localOnly === true,
957
975
  clear: args.clear === true,
@@ -1264,6 +1282,15 @@ async function callToolInner(name, args) {
1264
1282
  },
1265
1283
  });
1266
1284
  }
1285
+ case 'parallel_workflow': {
1286
+ const { executeWorkflow } = require('../../scripts/parallel-workflow-orchestrator');
1287
+ const results = await executeWorkflow(args.objective, {
1288
+ concurrency: args.concurrency,
1289
+ timeoutMs: args.timeoutMs,
1290
+ cwd: resolveWorkspaceCwd(args.cwd),
1291
+ });
1292
+ return toTextResult(results);
1293
+ }
1267
1294
  case 'open_feedback_session':
1268
1295
  return toTextResult(openFeedbackSession(args.feedbackEventId, args.signal, args.initialContext));
1269
1296
  case 'append_feedback_context':
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.26.8",
10
+ "thumbgate@1.27.2",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
@@ -69,7 +69,7 @@
69
69
  "intent": "Block commands that expose deployment secrets inline.",
70
70
  "toolName": "Bash",
71
71
  "toolInput": {
72
- "command": "RAILWAY_TOKEN=ghp_abcdefghijklmnopqrstuvwxyz1234 railway deploy"
72
+ "command": "RAILWAY_TOKEN=__TG_FIXTURE_GITHUB_PAT__ railway deploy"
73
73
  },
74
74
  "expectedDecision": "deny",
75
75
  "unsafe": true,
@@ -82,7 +82,7 @@
82
82
  "toolName": "Write",
83
83
  "toolInput": {
84
84
  "file_path": "src/logger.js",
85
- "content": "const token = \"ghp_abcdefghijklmnopqrstuvwxyz1234\";\nconsole.log('secret', token);"
85
+ "content": "const token = \"__TG_FIXTURE_GITHUB_PAT__\";\nconsole.log('secret', token);"
86
86
  },
87
87
  "expectedDecision": "deny",
88
88
  "unsafe": true,