thumbgate 1.26.6 → 1.26.7

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate-marketplace",
3
- "version": "1.26.6",
3
+ "version": "1.26.7",
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.6",
17
+ "version": "1.26.7",
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.6",
4
+ "version": "1.26.7",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky",
7
7
  "email": "ig5973700@gmail.com",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.26.6",
3
+ "version": "1.26.7",
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
@@ -533,18 +533,20 @@ Free and self-hosted users can invoke `search_lessons` directly through MCP, and
533
533
  For enterprise subscriptions, ThumbGate natively integrates with Google Cloud Platform and **Vertex AI** to route all agent checks through compliant Gemini models inside your corporate VPC.
534
534
 
535
535
  ### Zero-Friction Setup
536
- To instantly wire your local installation to Google Cloud, simply run:
536
+ To wire local ThumbGate scoring to Vertex AI, run:
537
537
  ```bash
538
538
  npx thumbgate setup-vertex
539
539
  ```
540
540
  * **Auto-Discovery:** Automatically detects your active authenticated `gcloud` session and active project ID.
541
541
  * **Auto-Enablement:** Programmatically enables the Vertex AI API in your project.
542
- * **Auto-Configuration:** Writes secure billing and project credentials directly to your local `.env` file.
542
+ * **Auto-Configuration:** Writes local Vertex routing settings to your `.env` file.
543
+
544
+ This command does **not** create or verify a live Dialogflow CX agent. On current Google Cloud CLI installs, the old alpha gcloud CX command group is not available; verify Conversational Agents / Dialogflow CX with the Google Cloud console or the official Dialogflow CX REST API (`projects.locations.agents`) before claiming a live DFCX deployment.
543
545
 
544
546
  ### Zero-Friction Cost Containment ($10/mo Hard Cap)
545
547
  Google Cloud budget alerts are "alert-only" and do not stop API traffic, risking unexpected bill shock. ThumbGate completely resolves this on the client side:
546
548
  * **Instant Shutdown:** ThumbGate maintains a lightweight, local token ledger and instantly halts outgoing API traffic the millisecond your monthly token spending approaches the **$10 limit** (500k tokens of Gemini 1.5 Flash).
547
- * **Bypasses Console Complexity:** Requires **zero** GCP web console setups, zero Pub/Sub topics, and zero Cloud Functions. Perfect for non-technical managers and teams.
549
+ * **Bypasses extra shutdown plumbing:** Requires no Pub/Sub or Cloud Functions for the local ThumbGate-side stop condition. You still need normal Google Cloud billing/API setup and live-agent verification for DFCX pilots.
548
550
 
549
551
  ---
550
552
 
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.26.6", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.26.7", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.26.6", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.26.7", "thumbgate", "gate-check"]
12
12
  }
13
13
  }
14
14
  }
@@ -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.6' };
234
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.26.7' };
235
235
  const COMMERCE_CATEGORIES = [
236
236
  'product_recommendation',
237
237
  'brand_compliance',
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.26.6",
10
+ "thumbgate@1.26.7",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
package/bin/cli.js CHANGED
@@ -700,10 +700,11 @@ async function setupVertex() {
700
700
  // 4. Print gorgeous success activation box
701
701
  console.log('');
702
702
  console.log(' ╭──────────────────────────────────────────────────────────╮');
703
- console.log(' │ 🎉 Vertex AI Setup Complete — ZERO FRICTION! │');
703
+ console.log(' │ Vertex AI Setup Complete │');
704
704
  console.log(' │ │');
705
- console.log(' │ ThumbGate is now fully wired to your GCP environment. │');
706
- console.log(' │ All agent checks will route securely via Vertex AI. │');
705
+ console.log(' │ ThumbGate wrote local Vertex routing config. │');
706
+ console.log(' │ This does not create or verify a Dialogflow CX agent. │');
707
+ console.log(' │ Verify DFCX with the console or Dialogflow CX REST API. │');
707
708
  console.log(' │ │');
708
709
  console.log(' │ Try a test run: │');
709
710
  console.log(' │ npx thumbgate feedback-self-test │');
@@ -2407,6 +2408,11 @@ function optimize() {
2407
2408
  doOptimize();
2408
2409
  }
2409
2410
 
2411
+ function syncGcp() {
2412
+ const { syncToGcp } = require(path.join(PKG_ROOT, 'adapters', 'gcp', 'sync.js'));
2413
+ syncToGcp();
2414
+ }
2415
+
2410
2416
  function cleanup() {
2411
2417
  console.log('Cleaning up ThumbGate processes...');
2412
2418
  try {
@@ -2963,7 +2969,7 @@ const SUBCOMMAND_HELP = {
2963
2969
  suggest: 'Usage: npx thumbgate suggest <gate-id>\n\nSuggest fixes for a specific gate based on lesson history.',
2964
2970
  cost: 'Usage: npx thumbgate cost [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nShow cumulative $ and tokens saved by PreToolUse gate blocks. Reads ~/.thumbgate/gate-stats.json.',
2965
2971
  savings: 'Usage: npx thumbgate savings [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nAlias for `thumbgate cost`.',
2966
- 'setup-vertex': 'Usage: npx thumbgate setup-vertex\n\nAuto-enable Vertex AI API on GCP and write secure credentials to local .env.',
2972
+ 'setup-vertex': 'Usage: npx thumbgate setup-vertex\n\nAuto-enable Vertex AI API on GCP and write local Vertex routing config to .env. This does not create or verify a Dialogflow CX agent; use the Dialogflow CX REST API or console for live-agent evidence.',
2967
2973
  brain: 'Usage: npx thumbgate brain [--write] [--json] [--limit=N]\n\nBuild the agent-readable "context brain" — a single artifact consolidating this\nrepo\'s lessons, prevention rules, active gates, and project context for a coding\nagent to read BEFORE acting. --write saves it to .thumbgate/BRAIN.md (versioned,\ndeterministic). --json emits the structured model. --limit caps lessons (default 15).',
2968
2974
  };
2969
2975
 
@@ -3120,6 +3126,9 @@ switch (COMMAND) {
3120
3126
  case 'cleanup':
3121
3127
  cleanup();
3122
3128
  break;
3129
+ case 'sync-gcp':
3130
+ syncGcp();
3131
+ break;
3123
3132
  case 'gate-check':
3124
3133
  gateCheck().catch((err) => {
3125
3134
  console.error(err && err.message ? err.message : err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.26.6",
3
+ "version": "1.26.7",
4
4
  "description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 36 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
5
5
  "homepage": "https://thumbgate.ai",
6
6
  "repository": {
@@ -63,6 +63,7 @@
63
63
  "scripts/context-manager.js",
64
64
  "scripts/contextfs.js",
65
65
  "scripts/conversation-context.js",
66
+ "scripts/dashboard-chat.js",
66
67
  "scripts/dashboard-render-spec.js",
67
68
  "scripts/dashboard.js",
68
69
  "scripts/decision-journal.js",
@@ -189,11 +189,18 @@
189
189
  .settings-card .team-value, .origin-value { font-size: 18px; font-weight: 700; color: var(--text); margin-top: 8px; word-break: break-word; }
190
190
  .origin-list, .layer-list, .routing-list { display: flex; flex-direction: column; gap: 12px; }
191
191
  .origin-note, .layer-note, .routing-note { font-size: 13px; color: var(--text-muted); line-height: 1.55; }
192
+ .enterprise-chat-layout { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(260px, 0.7fr); gap: 16px; align-items: start; }
193
+ .enterprise-chat-box { min-height: 110px; resize: vertical; width: 100%; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; color: var(--text); font-size: 14px; font-family: var(--font); line-height: 1.5; }
194
+ .enterprise-chat-box:focus { outline: none; border-color: var(--cyan); }
195
+ .enterprise-answer { margin-top: 14px; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px; padding: 16px; min-height: 92px; font-size: 14px; color: var(--text); line-height: 1.65; }
196
+ .enterprise-answer.blocked { border-color: rgba(248,113,113,0.45); background: rgba(248,113,113,0.07); }
197
+ .enterprise-source-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
198
+ .enterprise-source { font-size: 11px; border: 1px solid var(--border); border-radius: 999px; padding: 4px 9px; color: var(--text-muted); background: var(--bg-card); }
192
199
 
193
200
  @media (max-width: 700px) {
194
201
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
195
202
  .search-filters { flex-wrap: wrap; }
196
- .team-grid, .template-grid, .team-columns, .settings-grid, .generated-grid, .inventory-grid { grid-template-columns: 1fr; }
203
+ .team-grid, .template-grid, .team-columns, .settings-grid, .generated-grid, .inventory-grid, .enterprise-chat-layout { grid-template-columns: 1fr; }
197
204
  }
198
205
  </style>
199
206
  </head>
@@ -253,6 +260,19 @@
253
260
  <a class="stat-card" data-card-action="gates" onclick="selectCard(this,'gates');return false;" href="#" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view active checks"><div class="stat-label">Active Gates</div><div class="stat-value cyan" id="statGates">—</div></a>
254
261
  </div>
255
262
 
263
+ <div class="panel" id="chatPanel" style="margin-bottom:20px;">
264
+ <div style="display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:10px;">
265
+ <h2 style="margin:0;">💬 Chat with your data</h2>
266
+ <span style="font-size:13px;color:var(--text-muted);">Ask about your captured lessons, mistakes, and rules — answered by Gemini, grounded only in your data.</span>
267
+ </div>
268
+ <div id="chatMessages" style="max-height:360px;overflow-y:auto;margin-bottom:12px;display:none;padding-right:4px;"></div>
269
+ <div style="display:flex;gap:8px;">
270
+ <input id="chatInput" class="auth-input" style="flex:1;" placeholder="e.g. What mistakes have we made, and how do we avoid them?" onkeydown="if(event.key==='Enter'){event.preventDefault();sendChat();}" />
271
+ <button class="btn" id="chatSend" onclick="sendChat()">Ask</button>
272
+ </div>
273
+ <div id="chatHint" style="font-size:12px;color:var(--text-muted);margin-top:8px;">Powered by your captured lessons + Gemini. Set <code style="font-family:var(--mono);">GEMINI_API_KEY</code> (<code style="font-family:var(--mono);">npx thumbgate setup-vertex --write</code>) to enable.</div>
274
+ </div>
275
+
256
276
  <div class="panel" id="reviewDeltaPanel" style="margin-bottom:20px;">
257
277
  <div style="display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap;">
258
278
  <div>
@@ -284,6 +304,7 @@
284
304
  <div class="tab active" onclick="switchTab('search')">🔍 Search Memories</div>
285
305
  <div class="tab" onclick="switchTab('gates')">🛡️ Active Gates</div>
286
306
  <div class="tab" onclick="switchTab('team')">👥 Team</div>
307
+ <div class="tab" onclick="switchTab('enterprise')">🏢 Enterprise Chat</div>
287
308
  <div class="tab" onclick="switchTab('generated')">🧩 Generated Views</div>
288
309
  <div class="tab" onclick="switchTab('settings')">⚙️ Policy Origins</div>
289
310
  <div class="tab" onclick="switchTab('templates')">🧱 Gate Templates</div>
@@ -387,6 +408,33 @@
387
408
  </div>
388
409
  </div>
389
410
 
411
+ <!-- ENTERPRISE CHAT TAB -->
412
+ <div class="tab-content" id="tab-enterprise">
413
+ <div class="templates-section">
414
+ <h2>Enterprise Dialogflow Data Chat</h2>
415
+ <p class="template-summary">Ask questions over local ThumbGate feedback, lessons, gates, team posture, and Vertex/DFCX readiness. This local panel uses ThumbGate's DFCX-compatible guard before data access; it does not claim a live Google Dialogflow CX agent unless deployment evidence is configured.</p>
416
+ <div class="enterprise-chat-layout">
417
+ <div class="panel">
418
+ <h3>Chat With Local ThumbGate Data</h3>
419
+ <textarea class="enterprise-chat-box" id="enterpriseChatPrompt" placeholder="Ask: What mistakes are recurring? Which gates blocked the most? Is Vertex configured? What is our DFCX readiness?"></textarea>
420
+ <div style="display:flex;gap:10px;align-items:center;margin-top:12px;flex-wrap:wrap;">
421
+ <button class="btn" id="enterpriseChatBtn" onclick="sendEnterpriseChat()">Ask ThumbGate</button>
422
+ <button class="btn-outline" onclick="setEnterprisePrompt('Which gates are blocking risky actions?')">Gates</button>
423
+ <button class="btn-outline" onclick="setEnterprisePrompt('What feedback mistakes keep repeating?')">Feedback</button>
424
+ <button class="btn-outline" onclick="setEnterprisePrompt('Is Vertex and DFCX configured?')">Cloud</button>
425
+ </div>
426
+ <div class="enterprise-answer" id="enterpriseChatAnswer">Connect your dashboard, then ask about local ThumbGate data.</div>
427
+ <div class="enterprise-source-list" id="enterpriseChatSources"></div>
428
+ </div>
429
+ <div class="panel">
430
+ <h3>Enterprise Readiness</h3>
431
+ <div class="inventory-tools" id="enterpriseStatusCards"><div class="loading">Loading enterprise status...</div></div>
432
+ <div class="template-summary" style="margin-top:14px;margin-bottom:0;">Live DFCX proof must come from the Conversational Agents console, deployed webhook URL, Cloud Run logs, or the Dialogflow CX REST API <code style="font-family:var(--mono);font-size:12px;">projects.locations.agents</code>.</div>
433
+ </div>
434
+ </div>
435
+ </div>
436
+ </div>
437
+
390
438
  <!-- GENERATED TAB -->
391
439
  <div class="tab-content" id="tab-generated">
392
440
  <div class="templates-section">
@@ -637,6 +685,62 @@ function getHeaders() {
637
685
  return { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' };
638
686
  }
639
687
 
688
+ // --- Chat with your data ---------------------------------------------------
689
+ function chatEscape(s) {
690
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
691
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
692
+ });
693
+ }
694
+ function chatAppend(who, text) {
695
+ var messages = document.getElementById('chatMessages');
696
+ var row = document.createElement('div');
697
+ row.style.cssText = 'margin-bottom:14px;';
698
+ row.innerHTML = '<div style="font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:3px;">'
699
+ + (who === 'you' ? 'You' : 'ThumbGate') + '</div><div class="chat-body" style="line-height:1.5;">' + chatEscape(text) + '</div>';
700
+ messages.appendChild(row);
701
+ return row.querySelector('.chat-body');
702
+ }
703
+ function chatRenderAnswer(a) {
704
+ return chatEscape(a)
705
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
706
+ .replace(/\[(\d+)\]/g, '<sup style="color:var(--cyan);font-weight:600;">[$1]</sup>')
707
+ .replace(/\n/g, '<br>');
708
+ }
709
+ function chatRenderSources(sources) {
710
+ if (!sources || !sources.length) return '';
711
+ var items = sources.map(function (s, i) {
712
+ var label = chatEscape(String(s.title || s.id || '').slice(0, 64));
713
+ return '<span title="' + label + '" style="display:inline-block;font-size:11px;background:var(--cyan-dim);color:var(--cyan);padding:2px 7px;border-radius:5px;margin:4px 5px 0 0;">[' + (i + 1) + '] ' + label + '</span>';
714
+ }).join('');
715
+ return '<div style="margin-top:10px;">' + items + '</div>';
716
+ }
717
+ async function sendChat() {
718
+ var input = document.getElementById('chatInput');
719
+ var q = (input.value || '').trim();
720
+ if (!q) return;
721
+ var messages = document.getElementById('chatMessages');
722
+ var sendBtn = document.getElementById('chatSend');
723
+ messages.style.display = 'block';
724
+ chatAppend('you', q);
725
+ input.value = '';
726
+ sendBtn.disabled = true;
727
+ var pending = chatAppend('bot', 'Thinking…');
728
+ try {
729
+ var res = await fetch('/v1/chat', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ question: q }) });
730
+ var data = await res.json();
731
+ if (data && data.ok) {
732
+ pending.innerHTML = chatRenderAnswer(data.answer) + chatRenderSources(data.sources);
733
+ } else {
734
+ pending.innerHTML = '<em style="color:var(--text-muted);">' + chatEscape((data && data.message) || 'Chat is unavailable.') + '</em>';
735
+ }
736
+ } catch (e) {
737
+ pending.innerHTML = '<em style="color:var(--text-muted);">Chat request failed: ' + chatEscape(String((e && e.message) || e)) + '</em>';
738
+ } finally {
739
+ sendBtn.disabled = false;
740
+ messages.scrollTop = messages.scrollHeight;
741
+ }
742
+ }
743
+
640
744
  function hasBootstrapKey() {
641
745
  return LOCAL_PRO_BOOTSTRAP && Boolean(BOOTSTRAP_API_KEY);
642
746
  }
@@ -1038,9 +1142,14 @@ async function markReviewed() {
1038
1142
  try {
1039
1143
  var res = await fetch('/v1/dashboard/review-state', {
1040
1144
  method: 'POST',
1041
- headers: getHeaders()
1145
+ headers: getHeaders(),
1146
+ body: JSON.stringify({ reviewedAt: new Date().toISOString() })
1042
1147
  });
1043
- if (!res.ok) throw new Error('Failed to save review checkpoint');
1148
+ if (!res.ok) {
1149
+ var errText = '';
1150
+ try { errText = await res.text(); } catch (_) { errText = ''; }
1151
+ throw new Error(errText || 'Failed to save review checkpoint');
1152
+ }
1044
1153
  var body = await res.json();
1045
1154
  renderReviewDelta(body.reviewDelta || {});
1046
1155
  } catch (e) {
@@ -1078,6 +1187,7 @@ function renderDashboardData(data) {
1078
1187
  renderRegulatedProof(data.regulatedProof || {});
1079
1188
  renderTemplates(data.templateLibrary || {});
1080
1189
  renderInsights(data);
1190
+ loadEnterpriseDialogflowStatus();
1081
1191
  }
1082
1192
 
1083
1193
  function renderGeneratedViewToolbar(spec) {
@@ -1517,6 +1627,83 @@ function renderTemplates(templateLibrary) {
1517
1627
  : '<div class="empty">No check templates available</div>';
1518
1628
  }
1519
1629
 
1630
+ function renderEnterpriseStatus(status) {
1631
+ var target = document.getElementById('enterpriseStatusCards');
1632
+ if (!target) return;
1633
+ var vertex = status && status.vertex ? status.vertex : {};
1634
+ var dfcx = status && status.dfcx ? status.dfcx : {};
1635
+ var rows = [
1636
+ { name: 'Vertex routing', value: vertex.configured ? 'Configured' : 'Not configured', note: vertex.projectId ? ('Project: ' + vertex.projectId + ' · ' + (vertex.location || '')) : 'Run setup-vertex or set GOOGLE_VERTEX_PROJECT.' },
1637
+ { name: 'DFCX live agent', value: dfcx.liveAgentConfigured ? 'Env present' : 'Not proven', note: dfcx.verification || 'Verify with REST/console evidence.' },
1638
+ { name: 'Fulfillment proxy', value: dfcx.fulfillmentProxyConfigured ? 'Configured' : 'Not configured', note: 'Set THUMBGATE_DFCX_FULFILLMENT_URL for a deployed proxy.' },
1639
+ { name: 'gcloud CX command', value: 'Unsupported', note: 'Do not use the old alpha gcloud CX command group; use REST API or console.' }
1640
+ ];
1641
+ target.innerHTML = rows.map(function(row) {
1642
+ return '<div class="inventory-row"><div><div class="inventory-name">' + escHtml(row.name) + '</div><div class="inventory-subtitle">' + escHtml(row.note) + '</div></div><span class="remediation-action">' + escHtml(row.value) + '</span></div>';
1643
+ }).join('');
1644
+ }
1645
+
1646
+ async function loadEnterpriseDialogflowStatus() {
1647
+ if (!API_KEY || isDemo) {
1648
+ renderEnterpriseStatus({ vertex: {}, dfcx: { verification: 'Connect to load local status.' } });
1649
+ return;
1650
+ }
1651
+ try {
1652
+ var res = await fetch('/v1/enterprise/dialogflow/status', { headers: getHeaders() });
1653
+ if (!res.ok) throw new Error('status unavailable');
1654
+ renderEnterpriseStatus(await res.json());
1655
+ } catch (err) {
1656
+ var target = document.getElementById('enterpriseStatusCards');
1657
+ if (target) target.innerHTML = '<div class="empty">Enterprise status unavailable.</div>';
1658
+ }
1659
+ }
1660
+
1661
+ function setEnterprisePrompt(prompt) {
1662
+ var input = document.getElementById('enterpriseChatPrompt');
1663
+ if (input) input.value = prompt;
1664
+ }
1665
+
1666
+ async function sendEnterpriseChat() {
1667
+ var input = document.getElementById('enterpriseChatPrompt');
1668
+ var answer = document.getElementById('enterpriseChatAnswer');
1669
+ var sources = document.getElementById('enterpriseChatSources');
1670
+ var button = document.getElementById('enterpriseChatBtn');
1671
+ var prompt = input ? input.value.trim() : '';
1672
+ if (!prompt) {
1673
+ answer.textContent = 'Ask a question first.';
1674
+ return;
1675
+ }
1676
+ if (!API_KEY) {
1677
+ answer.textContent = 'Connect your dashboard before using Enterprise Chat.';
1678
+ return;
1679
+ }
1680
+ button.disabled = true;
1681
+ answer.className = 'enterprise-answer';
1682
+ answer.textContent = 'Running the DFCX guard and reading local dashboard data...';
1683
+ sources.innerHTML = '';
1684
+ try {
1685
+ var res = await fetch('/v1/enterprise/dialogflow/chat', {
1686
+ method: 'POST',
1687
+ headers: getHeaders(),
1688
+ body: JSON.stringify({ prompt: prompt })
1689
+ });
1690
+ var data = await res.json();
1691
+ if (!res.ok) throw new Error(data.detail || data.error || 'chat failed');
1692
+ answer.className = 'enterprise-answer' + (data.blocked ? ' blocked' : '');
1693
+ answer.textContent = data.answer || 'No answer returned.';
1694
+ var list = Array.isArray(data.sources) ? data.sources : [];
1695
+ sources.innerHTML = list.map(function(source) {
1696
+ return '<span class="enterprise-source">' + escHtml(source) + '</span>';
1697
+ }).join('');
1698
+ renderEnterpriseStatus(data.status || {});
1699
+ } catch (err) {
1700
+ answer.className = 'enterprise-answer blocked';
1701
+ answer.textContent = err.message || 'Enterprise chat failed.';
1702
+ } finally {
1703
+ button.disabled = false;
1704
+ }
1705
+ }
1706
+
1520
1707
  document.addEventListener('click', function(event) {
1521
1708
  var tagButton = event.target.closest('.tag[data-tag]');
1522
1709
  if (!tagButton) return;
@@ -2101,6 +2288,7 @@ function renderGateAuditChartFromData(gateAudit) {
2101
2288
  },
2102
2289
  });
2103
2290
  }
2291
+
2104
2292
  </script>
2105
2293
  </body>
2106
2294
  </html>
package/public/index.html CHANGED
@@ -20,7 +20,7 @@ __GOOGLE_SITE_VERIFICATION_META__
20
20
  <meta property="og:image" content="https://thumbgate.ai/og.png">
21
21
  <meta name="twitter:card" content="summary_large_image">
22
22
  <meta name="twitter:image" content="https://thumbgate.ai/og.png">
23
- <meta name="thumbgate-version" content="1.26.6">
23
+ <meta name="thumbgate-version" content="1.26.7">
24
24
  <meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agentic development cycle, AC/DC framework, Guide Generate Verify Solve, agent enforcement layer, save LLM tokens, reduce Claude API cost, reduce OpenAI cost, AI agent token savings, prevent LLM retries, prevent hallucination retries, stop AI token waste, pre-action checks, agent governance, Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode, workflow hardening, context engineering, AI authenticity, brand authenticity AI">
25
25
  <link rel="canonical" href="__APP_ORIGIN__/">
26
26
  <link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
@@ -1594,7 +1594,7 @@ __GA_BOOTSTRAP__
1594
1594
  <a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
1595
1595
  <a href="/blog">Blog</a>
1596
1596
  </div>
1597
- <span class="footer-copy">© 2026 ThumbGate · MIT License · npm v1.26.6</span>
1597
+ <span class="footer-copy">© 2026 ThumbGate · MIT License · npm v1.26.7</span>
1598
1598
  </div>
1599
1599
  </footer>
1600
1600
 
@@ -25,7 +25,7 @@
25
25
  "alternateName": "thumbgate",
26
26
  "applicationCategory": "DeveloperApplication",
27
27
  "operatingSystem": "Cross-platform, Node.js >=18.18.0",
28
- "softwareVersion": "1.26.6",
28
+ "softwareVersion": "1.26.7",
29
29
  "url": "https://thumbgate.ai/numbers",
30
30
  "dateModified": "2026-05-07",
31
31
  "creator": {
@@ -202,7 +202,7 @@
202
202
  <main class="container">
203
203
  <h1>The Numbers</h1>
204
204
  <p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
205
- <div class="freshness">Updated: 2026-05-07 · Version 1.26.6</div>
205
+ <div class="freshness">Updated: 2026-05-07 · Version 1.26.7</div>
206
206
  <div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
207
207
 
208
208
  <h2>Gate enforcement</h2>
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ // scripts/dashboard-chat.js
4
+ // -----------------------------------------------------------------------------
5
+ // "Chat with your data" — the dashboard chat backend. Answers a natural-language
6
+ // question about THIS install's ThumbGate data (captured lessons + prevention
7
+ // rules) by retrieving the most relevant lessons and asking Gemini to answer
8
+ // grounded ONLY in that retrieved context (RAG). No data leaves the box except
9
+ // the retrieved snippets + the question, sent to the configured Gemini endpoint.
10
+ //
11
+ // Enterprise framing: this is the in-product "chat with your governed data"
12
+ // experience. (The Dialogflow CX messenger widget is the separate path where a
13
+ // customer connects their own DFCX agent + the ThumbGate webhook gate.)
14
+ // -----------------------------------------------------------------------------
15
+
16
+ const path = require('path');
17
+
18
+ const GEMINI_ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models';
19
+ const DEFAULT_MODEL = 'gemini-2.5-flash';
20
+ const MAX_QUESTION_CHARS = 2000;
21
+ const MAX_CONTEXT_LESSONS = 8;
22
+
23
+ function resolveApiKey(opts = {}) {
24
+ return opts.apiKey || process.env.GEMINI_API_KEY || process.env.THUMBGATE_GEMINI_API_KEY || '';
25
+ }
26
+
27
+ // Retrieve the most relevant stored lessons for the question.
28
+ function retrieveContext(question, opts = {}) {
29
+ let searchLessons;
30
+ try {
31
+ ({ searchLessons } = require(path.join(__dirname, 'lesson-search')));
32
+ } catch (_) {
33
+ return [];
34
+ }
35
+ let res;
36
+ try {
37
+ res = searchLessons(String(question || ''), {
38
+ limit: MAX_CONTEXT_LESSONS,
39
+ feedbackDir: opts.feedbackDir,
40
+ });
41
+ } catch (_) {
42
+ return [];
43
+ }
44
+ const rows = (res && (res.results || res.lessons)) || [];
45
+ return rows.slice(0, MAX_CONTEXT_LESSONS).map((l) => ({
46
+ id: l.id,
47
+ signal: l.signal || l.feedback || '',
48
+ title: (l.title || '').replace(/^(?:MISTAKE|SUCCESS):\s*/i, '').slice(0, 160),
49
+ content: String(l.content || l.context || '').replace(/\s+/g, ' ').trim().slice(0, 600),
50
+ tags: l.tags || [],
51
+ })).filter((l) => l.content || l.title);
52
+ }
53
+
54
+ // Build a grounded RAG prompt. Pure function (testable).
55
+ function buildChatPrompt(question, lessons) {
56
+ const q = String(question || '').slice(0, MAX_QUESTION_CHARS).trim();
57
+ const context = (lessons || []).map((l, i) => {
58
+ const mark = /pos|up/i.test(l.signal) ? 'WORKED' : (/neg|down/i.test(l.signal) ? 'MISTAKE' : 'NOTE');
59
+ const tags = (l.tags || []).length ? ` [tags: ${l.tags.join(', ')}]` : '';
60
+ return `(${i + 1}) [${mark}] ${l.title || ''}${tags}\n ${l.content}`;
61
+ }).join('\n');
62
+
63
+ const system = [
64
+ 'You are ThumbGate\'s "chat with your data" assistant. Answer the user\'s question',
65
+ 'using ONLY the captured lessons below (this team\'s real feedback history).',
66
+ 'Be concise and specific. Cite the lesson numbers you used like [1], [3].',
67
+ 'If the lessons do not contain the answer, say so plainly — do not invent facts.',
68
+ ].join(' ');
69
+
70
+ return `${system}\n\n=== Captured lessons (your data) ===\n${context || '(no relevant lessons found)'}\n\n=== Question ===\n${q}`;
71
+ }
72
+
73
+ // Parse the Gemini generateContent response into plain text. Pure (testable).
74
+ function parseGeminiAnswer(body) {
75
+ const parts = body
76
+ && body.candidates
77
+ && body.candidates[0]
78
+ && body.candidates[0].content
79
+ && body.candidates[0].content.parts;
80
+ if (!Array.isArray(parts)) return '';
81
+ return parts.map((p) => (p && typeof p.text === 'string' ? p.text : '')).join('').trim();
82
+ }
83
+
84
+ // Answer a question grounded in this install's lessons. Returns
85
+ // { ok, answer, sources, model } or { ok:false, error, ... }.
86
+ async function answerDataQuestion(question, opts = {}) {
87
+ const q = String(question || '').trim();
88
+ if (!q) return { ok: false, error: 'empty_question', message: 'Ask a question about your data.' };
89
+ if (q.length > MAX_QUESTION_CHARS) {
90
+ return { ok: false, error: 'question_too_long', message: `Question exceeds ${MAX_QUESTION_CHARS} characters.` };
91
+ }
92
+
93
+ const apiKey = resolveApiKey(opts);
94
+ const lessons = retrieveContext(q, opts);
95
+ const sources = lessons.map((l) => ({ id: l.id, title: l.title, signal: l.signal }));
96
+
97
+ if (!apiKey) {
98
+ return {
99
+ ok: false,
100
+ error: 'no_api_key',
101
+ message: 'Chat is not configured. Set GEMINI_API_KEY (e.g. `npx thumbgate setup-vertex --write`) to enable "chat with your data".',
102
+ sources,
103
+ };
104
+ }
105
+
106
+ const model = opts.model || process.env.THUMBGATE_GEMINI_MODEL || DEFAULT_MODEL;
107
+ const prompt = buildChatPrompt(q, lessons);
108
+ const fetchImpl = opts.fetch || globalThis.fetch;
109
+
110
+ try {
111
+ const res = await fetchImpl(`${GEMINI_ENDPOINT}/${encodeURIComponent(model)}:generateContent`, {
112
+ method: 'POST',
113
+ headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
114
+ body: JSON.stringify({
115
+ contents: [{ role: 'user', parts: [{ text: prompt }] }],
116
+ generationConfig: { temperature: 0.2, maxOutputTokens: 1024 },
117
+ }),
118
+ });
119
+ const json = await res.json().catch(() => ({}));
120
+ if (!res.ok) {
121
+ const msg = (json && json.error && json.error.message) ? String(json.error.message).split('\n')[0] : `HTTP ${res.status}`;
122
+ return { ok: false, error: 'gemini_error', status: res.status, message: msg, sources };
123
+ }
124
+ const answer = parseGeminiAnswer(json);
125
+ return { ok: true, answer: answer || '(no answer returned)', sources, model: json.modelVersion || model };
126
+ } catch (err) {
127
+ return { ok: false, error: 'network', message: err && err.message ? err.message : String(err), sources };
128
+ }
129
+ }
130
+
131
+ module.exports = {
132
+ answerDataQuestion,
133
+ buildChatPrompt,
134
+ parseGeminiAnswer,
135
+ retrieveContext,
136
+ DEFAULT_MODEL,
137
+ MAX_QUESTION_CHARS,
138
+ };
@@ -8,9 +8,22 @@ function getStatuslineMeta(options = {}) {
8
8
  const pkg = require(path.join(__dirname, '..', 'package.json'));
9
9
  const env = options.env || process.env;
10
10
  const homeDir = options.homeDir || env.HOME || env.USERPROFILE || '.';
11
+ const fs = require('fs');
11
12
 
12
13
  // Enterprise detection based on key prefix
13
- const apiKey = env.THUMBGATE_API_KEY || env.THUMBGATE_OPERATOR_KEY || '';
14
+ let apiKey = env.THUMBGATE_API_KEY || env.THUMBGATE_OPERATOR_KEY || '';
15
+
16
+ // Fallback to reading from disk if not in env
17
+ if (!apiKey) {
18
+ try {
19
+ const configPath = path.join(homeDir, '.config', 'thumbgate', 'operator.json');
20
+ if (fs.existsSync(configPath)) {
21
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
22
+ apiKey = config.operatorKey || config.apiKey || '';
23
+ }
24
+ } catch (_) { /* ignore disk read errors */ }
25
+ }
26
+
14
27
  let activeTier = 'Free';
15
28
 
16
29
  if (apiKey.startsWith('tg_op_') || apiKey.startsWith('tg_creator_')) {
@@ -93,19 +93,17 @@ fi
93
93
  LINK_STATE="offline"
94
94
  UP_URL=""; DOWN_URL=""; DASHBOARD_URL=""; LESSONS_URL=""
95
95
  DASHBOARD_LABEL="Dashboard"; LESSONS_LABEL="Lessons"
96
- if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
97
- _LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
98
- if [ -n "$_LINKS_JSON" ]; then
99
- eval "$(echo "$_LINKS_JSON" | jq -r '
100
- @sh "LINK_STATE=\(.state // "offline")",
101
- @sh "UP_URL=\(.upUrl // "")",
102
- @sh "DOWN_URL=\(.downUrl // "")",
103
- @sh "DASHBOARD_URL=\(.dashboardUrl // "")",
104
- @sh "LESSONS_URL=\(.lessonsUrl // "")",
105
- @sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
106
- @sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
107
- ' 2>/dev/null)"
108
- fi
96
+ _LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
97
+ if [ -n "$_LINKS_JSON" ]; then
98
+ eval "$(echo "$_LINKS_JSON" | jq -r '
99
+ @sh "LINK_STATE=\(.state // "offline")",
100
+ @sh "UP_URL=\(.upUrl // "")",
101
+ @sh "DOWN_URL=\(.downUrl // "")",
102
+ @sh "DASHBOARD_URL=\(.dashboardUrl // "")",
103
+ @sh "LESSONS_URL=\(.lessonsUrl // "")",
104
+ @sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
105
+ @sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
106
+ ' 2>/dev/null)"
109
107
  fi
110
108
 
111
109
  # ── ThumbGate package metadata ────────────────────────────────────────
@@ -120,15 +118,13 @@ fi
120
118
 
121
119
  # ── Repo context (branch / work item / PR) ───────────────────────────
122
120
  BRANCH_NAME=""; WORK_ITEM_LABEL=""; PR_LABEL=""
123
- if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
124
- _CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
125
- if [[ -n "$_CONTEXT_JSON" ]]; then
126
- eval "$(echo "$_CONTEXT_JSON" | jq -r '
127
- @sh "BRANCH_NAME=\(.branchName // "")",
128
- @sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
129
- @sh "PR_LABEL=\(.prLabel // "")"
130
- ' 2>/dev/null)"
131
- fi
121
+ _CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
122
+ if [[ -n "$_CONTEXT_JSON" ]]; then
123
+ eval "$(echo "$_CONTEXT_JSON" | jq -r '
124
+ @sh "BRANCH_NAME=\(.branchName // "")",
125
+ @sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
126
+ @sh "PR_LABEL=\(.prLabel // "")"
127
+ ' 2>/dev/null)"
132
128
  fi
133
129
 
134
130
  # ── Control Tower stats ──────────────────────────────────────────
@@ -144,16 +140,14 @@ fi
144
140
 
145
141
  # ── Latest lesson (data available for extensions; not rendered in statusbar) ──
146
142
  LESSON_TEXT=""; LESSON_ID=""; LESSON_LABEL=""; LESSON_LINK=""
147
- if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
148
- _LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
149
- if [[ -n "$_LESSON_JSON" ]]; then
150
- eval "$(echo "$_LESSON_JSON" | jq -r '
151
- @sh "LESSON_TEXT=\(.text // "")",
152
- @sh "LESSON_ID=\(.lessonId // "")",
153
- @sh "LESSON_LABEL=\(.label // "")",
154
- @sh "LESSON_LINK=\(.link // "")"
155
- ' 2>/dev/null)"
156
- fi
143
+ _LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
144
+ if [[ -n "$_LESSON_JSON" ]]; then
145
+ eval "$(echo "$_LESSON_JSON" | jq -r '
146
+ @sh "LESSON_TEXT=\(.text // "")",
147
+ @sh "LESSON_ID=\(.lessonId // "")",
148
+ @sh "LESSON_LABEL=\(.label // "")",
149
+ @sh "LESSON_LINK=\(.link // "")"
150
+ ' 2>/dev/null)"
157
151
  fi
158
152
 
159
153
  # ── Colors ────────────────────────────────────────────────────────
@@ -207,7 +201,7 @@ if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
207
201
  LINE="${D}${LINE}${RST} · no feedback yet"
208
202
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
209
203
 
210
- LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
204
+ LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST}"
211
205
  [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
212
206
 
213
207
  printf '%b\n' "$LINE"
@@ -220,7 +214,7 @@ else
220
214
  [[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}☠${RST}"
221
215
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
222
216
 
223
- LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
217
+ LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST}"
224
218
  [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
225
219
 
226
220
  printf '%b\n' "$LINE"
package/src/api/server.js CHANGED
@@ -166,6 +166,9 @@ const {
166
166
  readDashboardReviewState,
167
167
  writeDashboardReviewState,
168
168
  } = require('../../scripts/dashboard');
169
+ const {
170
+ guardDfcxWebhook,
171
+ } = require('../../adapters/gcp/dfcx-webhook-gate');
169
172
  const {
170
173
  buildDashboardRenderSpec,
171
174
  } = require('../../scripts/dashboard-render-spec');
@@ -1448,6 +1451,178 @@ async function loadLiveDashboardDataOrRespondProblem(res, parsed, feedbackDir, i
1448
1451
  }
1449
1452
  }
1450
1453
 
1454
+ function buildEnterpriseDialogflowStatus(env = process.env) {
1455
+ const vertexProject = normalizeNullableText(env.VERTEX_PROJECT_ID)
1456
+ || normalizeNullableText(env.GOOGLE_VERTEX_PROJECT);
1457
+ const vertexLocation = normalizeNullableText(env.GOOGLE_VERTEX_LOCATION)
1458
+ || normalizeNullableText(env.VERTEX_LOCATION)
1459
+ || 'us-central1';
1460
+ const dfcxFulfillmentUrl = normalizeNullableText(env.THUMBGATE_DFCX_FULFILLMENT_URL);
1461
+ const dfcxAgentId = normalizeNullableText(env.THUMBGATE_DFCX_AGENT_ID);
1462
+ const dfcxLocation = normalizeNullableText(env.THUMBGATE_DFCX_LOCATION);
1463
+
1464
+ return {
1465
+ mode: 'local-dashboard',
1466
+ vertex: {
1467
+ configured: Boolean(vertexProject),
1468
+ projectId: vertexProject,
1469
+ location: vertexLocation,
1470
+ providerMode: normalizeNullableText(env.THUMBGATE_PROVIDER_MODE) || null,
1471
+ },
1472
+ dfcx: {
1473
+ apiSurface: 'Dialogflow CX REST API: projects.locations.agents',
1474
+ liveAgentConfigured: Boolean(dfcxAgentId && dfcxLocation),
1475
+ agentId: dfcxAgentId,
1476
+ location: dfcxLocation,
1477
+ fulfillmentProxyConfigured: Boolean(dfcxFulfillmentUrl),
1478
+ fulfillmentUrlConfigured: Boolean(dfcxFulfillmentUrl),
1479
+ gcloudCxCommandSupported: false,
1480
+ verification: dfcxAgentId && dfcxLocation
1481
+ ? 'Agent metadata is present in env; verify via REST/console before production claims.'
1482
+ : 'No live DFCX agent env configured. Use REST/console/deployed webhook evidence before claiming a live agent.',
1483
+ },
1484
+ chat: {
1485
+ available: true,
1486
+ source: 'local ThumbGate dashboard data',
1487
+ guard: 'DFCX-compatible pre-action gate adapter',
1488
+ },
1489
+ };
1490
+ }
1491
+
1492
+ function normalizeEnterpriseChatPrompt(value) {
1493
+ const text = normalizeNullableText(value);
1494
+ if (!text) return null;
1495
+ return text.slice(0, 800);
1496
+ }
1497
+
1498
+ function classifyEnterpriseChatTopic(prompt) {
1499
+ const lower = String(prompt || '').toLowerCase();
1500
+ if (/gate|block|deny|prevent|guard/.test(lower)) return 'gates';
1501
+ if (/lesson|memory|feedback|thumb|mistake|negative|positive/.test(lower)) return 'feedback';
1502
+ if (/team|agent|org|enterprise|rollout/.test(lower)) return 'team';
1503
+ if (/token|cost|saving|budget|spend/.test(lower)) return 'cost';
1504
+ if (/vertex|gcp|google|dialogflow|dfcx|cloud/.test(lower)) return 'cloud';
1505
+ return 'overview';
1506
+ }
1507
+
1508
+ function containsUnsafeEnterpriseChatInput(prompt) {
1509
+ return /[;&|`$<>\\]/.test(String(prompt || ''));
1510
+ }
1511
+
1512
+ function compactNumber(value) {
1513
+ const n = Number(value || 0);
1514
+ return Number.isFinite(n) ? n : 0;
1515
+ }
1516
+
1517
+ function buildEnterpriseChatAnswer(prompt, dashboardData, status) {
1518
+ const topic = classifyEnterpriseChatTopic(prompt);
1519
+ const approval = dashboardData.approval || {};
1520
+ const gates = Array.isArray(dashboardData.gates) ? dashboardData.gates : [];
1521
+ const gateStats = dashboardData.gateStats || {};
1522
+ const team = dashboardData.team || {};
1523
+ const tokenSavings = dashboardData.tokenSavings || {};
1524
+ const lessonPipeline = dashboardData.lessonPipeline || {};
1525
+ const lines = [];
1526
+ const sources = ['local dashboard data'];
1527
+
1528
+ if (topic === 'feedback') {
1529
+ lines.push(`Feedback total: ${compactNumber(approval.total)} (${compactNumber(approval.positive)} positive, ${compactNumber(approval.negative)} negative).`);
1530
+ lines.push(`Lesson pipeline: ${compactNumber(lessonPipeline.lessons || lessonPipeline.generated || 0)} lessons visible in the current dashboard snapshot.`);
1531
+ sources.push('feedback log', 'lesson pipeline');
1532
+ } else if (topic === 'gates') {
1533
+ lines.push(`Active gates: ${gates.length || compactNumber(gateStats.totalGates)}.`);
1534
+ lines.push(`Blocked actions recorded: ${compactNumber(gateStats.blocked || gateStats.denied || gateStats.totalBlocked)}.`);
1535
+ if (gates[0]) lines.push(`Example gate: ${gates[0].name || gates[0].id || 'unnamed gate'}.`);
1536
+ sources.push('gate stats');
1537
+ } else if (topic === 'team') {
1538
+ lines.push(`Team dashboard is available in this local Enterprise view.`);
1539
+ lines.push(`Tracked agents: ${compactNumber(team.totalAgents || team.agentCount || 0)}; risky agents: ${compactNumber(team.riskyAgents || team.highRiskAgents || 0)}.`);
1540
+ sources.push('team dashboard');
1541
+ } else if (topic === 'cost') {
1542
+ lines.push(`Estimated token savings: ${tokenSavings.dollarsSavedDisplay || '$0.00'} from ${compactNumber(tokenSavings.blockedCalls)} blocked calls.`);
1543
+ lines.push('Google Cloud budget alerts are evidence for spend visibility; ThumbGate-side stop conditions must be verified separately before calling them a hard cap.');
1544
+ sources.push('token savings', 'budget posture');
1545
+ } else if (topic === 'cloud') {
1546
+ lines.push(status.vertex.configured
1547
+ ? `Vertex routing config is present for project ${status.vertex.projectId} (${status.vertex.location}).`
1548
+ : 'Vertex routing config is not present in this server environment.');
1549
+ lines.push(status.dfcx.liveAgentConfigured
1550
+ ? `DFCX env has agent ${status.dfcx.agentId} in ${status.dfcx.location}; verify it with REST/console before production claims.`
1551
+ : 'No live DFCX agent is configured in env. Do not use the old alpha gcloud CX command group; verify agents with the Dialogflow CX REST API or console.');
1552
+ sources.push('enterprise cloud status');
1553
+ } else {
1554
+ lines.push('Ask about feedback, lessons, active gates, team rollout, token savings, or Vertex/DFCX readiness.');
1555
+ lines.push(`Current local snapshot: ${compactNumber(approval.total)} feedback events and ${gates.length || compactNumber(gateStats.totalGates)} active gates.`);
1556
+ }
1557
+
1558
+ return {
1559
+ topic,
1560
+ answer: lines.join(' '),
1561
+ sources,
1562
+ };
1563
+ }
1564
+
1565
+ async function answerEnterpriseDialogflowChat({ prompt, feedbackDir, parsed }) {
1566
+ const normalizedPrompt = normalizeEnterpriseChatPrompt(prompt);
1567
+ if (!normalizedPrompt) {
1568
+ throw createHttpError(400, 'prompt is required');
1569
+ }
1570
+ const status = buildEnterpriseDialogflowStatus();
1571
+ if (containsUnsafeEnterpriseChatInput(normalizedPrompt)) {
1572
+ return {
1573
+ ok: false,
1574
+ blocked: true,
1575
+ answer: 'This prompt contains unsafe control characters and was blocked before data access.',
1576
+ status,
1577
+ dfcx: {
1578
+ blocked: true,
1579
+ evaluation: {
1580
+ allowed: false,
1581
+ gate: 'enterprise-chat-unsafe-input',
1582
+ severity: 'critical',
1583
+ },
1584
+ },
1585
+ sources: ['enterprise input guard'],
1586
+ };
1587
+ }
1588
+
1589
+ const dashboardResult = await buildLiveDashboardData(parsed, feedbackDir);
1590
+ const dashboardData = dashboardResult.data;
1591
+ const chat = buildEnterpriseChatAnswer(normalizedPrompt, dashboardData, status);
1592
+ const dfcxRequest = {
1593
+ fulfillmentInfo: { tag: 'chat-with-data' },
1594
+ sessionInfo: {
1595
+ session: 'local-dashboard/enterprise-chat',
1596
+ parameters: {
1597
+ topic: chat.topic,
1598
+ prompt_key: normalizedPrompt.toLowerCase().replace(/[^a-z0-9._ -]/g, '').slice(0, 64),
1599
+ },
1600
+ },
1601
+ languageCode: 'en',
1602
+ };
1603
+ const guarded = await guardDfcxWebhook(
1604
+ dfcxRequest,
1605
+ async () => ({
1606
+ fulfillment_response: { messages: [{ text: { text: [chat.answer] } }] },
1607
+ session_info: { parameters: { thumbgate_topic: chat.topic } },
1608
+ }),
1609
+ { blockOnRepeat: false },
1610
+ );
1611
+
1612
+ return {
1613
+ ok: !guarded.blocked,
1614
+ blocked: Boolean(guarded.blocked),
1615
+ answer: guarded.blocked ? 'ThumbGate blocked this enterprise chat turn before data access.' : chat.answer,
1616
+ status,
1617
+ dfcx: {
1618
+ blocked: Boolean(guarded.blocked),
1619
+ evaluation: guarded.evaluation,
1620
+ response: guarded.response,
1621
+ },
1622
+ sources: chat.sources,
1623
+ };
1624
+ }
1625
+
1451
1626
  function buildLossAnalyticsResponse(data, summaryOptions) {
1452
1627
  return {
1453
1628
  window: data.analytics.window || summaryOptions,
@@ -2068,6 +2243,7 @@ window.THUMBGATE_DASHBOARD_BOOTSTRAP = { enabled: ${bootstrapActive ? 'true' : '
2068
2243
  <p>This lightweight npm dashboard is bundled without marketing assets, so installs stay small while core feedback, lessons, and API routes remain available.</p>
2069
2244
  <div class="grid">
2070
2245
  <a class="card" href="/v1/dashboard"><strong>Dashboard JSON</strong><span>Inspect feedback totals, lesson counts, and Reliability Gateway health.</span></a>
2246
+ <a class="card" href="/v1/enterprise/dialogflow/status"><strong>Enterprise Dialogflow Data Chat</strong><span>Check Vertex/DFCX readiness and use /v1/enterprise/dialogflow/chat to query local ThumbGate data through the DFCX guard.</span></a>
2071
2247
  <a class="card" href="/lessons"><strong>Lessons</strong><span>Review remembered thumbs-up/down lessons and enforcement context.</span></a>
2072
2248
  <a class="card" href="/health"><strong>Health</strong><span>Verify the installed package version and runtime status.</span></a>
2073
2249
  </div>
@@ -6747,6 +6923,20 @@ ${hidden}
6747
6923
  return;
6748
6924
  }
6749
6925
 
6926
+ // Chat with your data — RAG over this install's captured lessons, answered
6927
+ // by Gemini grounded only in the retrieved context. Powers the dashboard
6928
+ // "Chat with your data" panel.
6929
+ if (req.method === 'POST' && pathname === '/v1/chat') {
6930
+ const body = await parseJsonBody(req);
6931
+ const { answerDataQuestion } = require('../../scripts/dashboard-chat');
6932
+ const result = await answerDataQuestion(body.question || body.q || body.message, {
6933
+ feedbackDir: requestFeedbackPaths.FEEDBACK_DIR,
6934
+ model: typeof body.model === 'string' ? body.model : undefined,
6935
+ });
6936
+ sendJson(res, result.ok ? 200 : (result.error === 'no_api_key' ? 503 : 400), result);
6937
+ return;
6938
+ }
6939
+
6750
6940
  // Server-Sent Events stream of live feedback / rule-regen / gate events.
6751
6941
  // Dashboard clients subscribe once (with the same Bearer auth already
6752
6942
  // required for /v1/feedback/stats) and receive pushed events as they
@@ -6802,6 +6992,22 @@ ${hidden}
6802
6992
  return;
6803
6993
  }
6804
6994
 
6995
+ if (req.method === 'GET' && pathname === '/v1/enterprise/dialogflow/status') {
6996
+ sendJson(res, 200, buildEnterpriseDialogflowStatus());
6997
+ return;
6998
+ }
6999
+
7000
+ if (req.method === 'POST' && pathname === '/v1/enterprise/dialogflow/chat') {
7001
+ const body = await parseJsonBody(req, 16 * 1024);
7002
+ const result = await answerEnterpriseDialogflowChat({
7003
+ prompt: body.prompt || body.message || body.query,
7004
+ feedbackDir: requestFeedbackDir,
7005
+ parsed,
7006
+ });
7007
+ sendJson(res, 200, result);
7008
+ return;
7009
+ }
7010
+
6805
7011
  if (req.method === 'GET' && pathname === '/v1/intents/catalog') {
6806
7012
  const mcpProfile = parsed.searchParams.get('mcpProfile') || undefined;
6807
7013
  const bundleId = parsed.searchParams.get('bundleId') || undefined;
@@ -7990,7 +8196,12 @@ ${hidden}
7990
8196
 
7991
8197
  // POST /v1/dashboard/review-state -- mark current dashboard state as reviewed
7992
8198
  if (req.method === 'POST' && pathname === '/v1/dashboard/review-state') {
8199
+ const body = await parseJsonBody(req);
7993
8200
  const snapshot = buildReviewSnapshot(requestFeedbackDir);
8201
+ // Override snapshot timestamp with client-provided one if available
8202
+ if (body && body.reviewedAt) {
8203
+ snapshot.reviewedAt = body.reviewedAt;
8204
+ }
7994
8205
  writeDashboardReviewState(requestFeedbackDir, snapshot);
7995
8206
  const data = generateDashboard(requestFeedbackDir, {
7996
8207
  reviewBaseline: snapshot,
@@ -8306,6 +8517,9 @@ module.exports = {
8306
8517
  resolveLocalPageBootstrap,
8307
8518
  getPublicMcpTools,
8308
8519
  getServerCardTools,
8520
+ buildEnterpriseDialogflowStatus,
8521
+ buildEnterpriseChatAnswer,
8522
+ answerEnterpriseDialogflowChat,
8309
8523
  },
8310
8524
  };
8311
8525