thumbgate 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/README.md +134 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +18 -0
- package/.claude-plugin/bundle/server/index.js +24 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.well-known/mcp/server-card.json +231 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/adapters/README.md +9 -0
- package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
- package/adapters/chatgpt/INSTALL.md +83 -0
- package/adapters/chatgpt/openapi.yaml +1281 -0
- package/adapters/claude/.mcp.json +14 -0
- package/adapters/codex/config.toml +9 -0
- package/adapters/gemini/function-declarations.json +224 -0
- package/adapters/mcp/server-stdio.js +788 -0
- package/adapters/opencode/opencode.json +15 -0
- package/bin/cli.js +1483 -0
- package/bin/memory.sh +64 -0
- package/bin/obsidian-sync.sh +20 -0
- package/bin/postinstall.js +37 -0
- package/config/build-metadata.json +4 -0
- package/config/e2e-critical-flows.json +45 -0
- package/config/gate-templates.json +77 -0
- package/config/gates/claim-verification.json +29 -0
- package/config/gates/computer-use.json +39 -0
- package/config/gates/default.json +117 -0
- package/config/github-about.json +25 -0
- package/config/mcp-allowlists.json +135 -0
- package/config/model-tiers.json +33 -0
- package/config/partner-routing.json +132 -0
- package/config/policy-bundles/constrained-v1.json +64 -0
- package/config/policy-bundles/default-v1.json +91 -0
- package/config/rubrics/default-v1.json +52 -0
- package/config/skill-packs/react-testing.json +23 -0
- package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
- package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
- package/config/skill-specs/pr-reviewer.json +9 -0
- package/config/skill-specs/release-status.json +9 -0
- package/config/skill-specs/ticket-triage.json +9 -0
- package/config/subagent-profiles.json +32 -0
- package/config/tessl-tiles.json +29 -0
- package/config/thumbgate-settings.managed.json +12 -0
- package/openapi/openapi.yaml +1281 -0
- package/package.json +286 -0
- package/plugins/amp-skill/INSTALL.md +52 -0
- package/plugins/amp-skill/SKILL.md +64 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
- package/plugins/claude-codex-bridge/.mcp.json +12 -0
- package/plugins/claude-codex-bridge/INSTALL.md +43 -0
- package/plugins/claude-codex-bridge/README.md +46 -0
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
- package/plugins/claude-skill/INSTALL.md +55 -0
- package/plugins/claude-skill/SKILL.md +46 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
- package/plugins/codex-profile/.mcp.json +12 -0
- package/plugins/codex-profile/AGENTS.md +20 -0
- package/plugins/codex-profile/INSTALL.md +66 -0
- package/plugins/codex-profile/README.md +37 -0
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
- package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
- package/plugins/cursor-marketplace/LICENSE +21 -0
- package/plugins/cursor-marketplace/README.md +124 -0
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
- package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
- package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
- package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
- package/plugins/cursor-marketplace/mcp.json +12 -0
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
- package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
- package/plugins/gemini-extension/INSTALL.md +92 -0
- package/plugins/gemini-extension/gemini_prompt.txt +14 -0
- package/plugins/gemini-extension/tool_contract.json +45 -0
- package/plugins/opencode-profile/INSTALL.md +57 -0
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +400 -0
- package/public/dashboard.html +1093 -0
- package/public/guide.html +317 -0
- package/public/index.html +1195 -0
- package/public/learn/agent-harness-pattern.html +180 -0
- package/public/learn/ai-agent-persistent-memory.html +202 -0
- package/public/learn/learn.css +45 -0
- package/public/learn/mcp-pre-action-gates-explained.html +172 -0
- package/public/learn/stop-ai-agent-force-push.html +134 -0
- package/public/learn/vibe-coding-safety-net.html +142 -0
- package/public/learn.html +213 -0
- package/public/lessons.html +650 -0
- package/public/vercel.json +8 -0
- package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
- package/scripts/a2ui-engine.js +73 -0
- package/scripts/access-anomaly-detector.js +12 -0
- package/scripts/adk-consolidator.js +266 -0
- package/scripts/agent-readiness.js +220 -0
- package/scripts/agent-security-hardening.js +227 -0
- package/scripts/agentic-data-pipeline.js +847 -0
- package/scripts/analytics-report.js +328 -0
- package/scripts/analytics-window.js +158 -0
- package/scripts/async-job-runner.js +1001 -0
- package/scripts/audit-trail.js +398 -0
- package/scripts/auto-promote-gates.js +293 -0
- package/scripts/auto-wire-hooks.js +316 -0
- package/scripts/autonomous-sales-agent.js +39 -0
- package/scripts/autoresearch-runner.js +216 -0
- package/scripts/background-agent-governance.js +237 -0
- package/scripts/behavioral-extraction.js +93 -0
- package/scripts/belief-update.js +84 -0
- package/scripts/billing.js +2438 -0
- package/scripts/bot-detector.js +50 -0
- package/scripts/budget-guard.js +173 -0
- package/scripts/build-claude-mcpb.js +189 -0
- package/scripts/build-metadata.js +97 -0
- package/scripts/check-congruence.js +322 -0
- package/scripts/cli-feedback.js +135 -0
- package/scripts/cli-telemetry.js +87 -0
- package/scripts/cloudflare-dynamic-sandbox.js +315 -0
- package/scripts/code-reasoning.js +350 -0
- package/scripts/codegraph-context.js +466 -0
- package/scripts/commercial-offer.js +56 -0
- package/scripts/computer-use-firewall.js +250 -0
- package/scripts/context-engine.js +694 -0
- package/scripts/contextfs.js +1287 -0
- package/scripts/conversation-context.js +119 -0
- package/scripts/creator-campaigns.js +239 -0
- package/scripts/daemon-manager.js +108 -0
- package/scripts/daily-digest.js +11 -0
- package/scripts/dashboard-render-spec.js +395 -0
- package/scripts/dashboard.js +1058 -0
- package/scripts/data-governance.js +173 -0
- package/scripts/delegation-runtime.js +900 -0
- package/scripts/deploy-gcp.sh +44 -0
- package/scripts/deploy-policy.js +231 -0
- package/scripts/disagreement-mining.js +315 -0
- package/scripts/dispatch-brief.js +159 -0
- package/scripts/distribution-surfaces.js +44 -0
- package/scripts/dpo-optimizer.js +206 -0
- package/scripts/ensure-repo-bootstrap.js +129 -0
- package/scripts/ephemeral-agent-store.js +219 -0
- package/scripts/eval-harness.js +56 -0
- package/scripts/evolution-state.js +241 -0
- package/scripts/experiment-tracker.js +267 -0
- package/scripts/export-databricks-bundle.js +242 -0
- package/scripts/export-dpo-pairs.js +344 -0
- package/scripts/export-kto-pairs.js +309 -0
- package/scripts/export-training.js +450 -0
- package/scripts/failure-diagnostics.js +558 -0
- package/scripts/feedback-attribution.js +313 -0
- package/scripts/feedback-fallback.js +110 -0
- package/scripts/feedback-history-distiller.js +391 -0
- package/scripts/feedback-inbox-read.js +162 -0
- package/scripts/feedback-loop.js +1887 -0
- package/scripts/feedback-paths.js +145 -0
- package/scripts/feedback-quality.js +139 -0
- package/scripts/feedback-root-consolidator.js +238 -0
- package/scripts/feedback-schema.js +426 -0
- package/scripts/feedback-session.js +286 -0
- package/scripts/feedback-to-memory.js +185 -0
- package/scripts/feedback-to-rules.js +164 -0
- package/scripts/filesystem-search.js +405 -0
- package/scripts/funnel-analytics.js +35 -0
- package/scripts/gate-satisfy.js +42 -0
- package/scripts/gate-stats.js +116 -0
- package/scripts/gate-templates.js +70 -0
- package/scripts/gates-engine.js +816 -0
- package/scripts/generate-paperbanana-diagrams.sh +99 -0
- package/scripts/generate-pretool-hook.sh +40 -0
- package/scripts/github-about.js +350 -0
- package/scripts/github-outreach.js +65 -0
- package/scripts/gtm-revenue-loop.js +520 -0
- package/scripts/hallucination-detector.js +226 -0
- package/scripts/hf-papers.js +317 -0
- package/scripts/history-distiller.js +200 -0
- package/scripts/hook-auto-capture.sh +100 -0
- package/scripts/hook-stop-pr-thread-check.sh +68 -0
- package/scripts/hook-stop-self-score.sh +51 -0
- package/scripts/hook-stop-verify-deploy.sh +31 -0
- package/scripts/hook-thumbgate-cache-updater.js +48 -0
- package/scripts/hook-verify-before-done.sh +20 -0
- package/scripts/hosted-config.js +156 -0
- package/scripts/hybrid-feedback-context.js +675 -0
- package/scripts/install-mcp.js +159 -0
- package/scripts/intent-router.js +392 -0
- package/scripts/internal-agent-bootstrap.js +490 -0
- package/scripts/jsonl-watcher.js +155 -0
- package/scripts/lesson-db.js +613 -0
- package/scripts/lesson-inference.js +310 -0
- package/scripts/lesson-retrieval.js +95 -0
- package/scripts/lesson-rotation.js +137 -0
- package/scripts/lesson-search.js +644 -0
- package/scripts/lesson-synthesis.js +196 -0
- package/scripts/license.js +50 -0
- package/scripts/local-model-profile.js +384 -0
- package/scripts/markdown-escape.js +12 -0
- package/scripts/marketing-experiment.js +671 -0
- package/scripts/mcp-config.js +149 -0
- package/scripts/mcp-policy.js +99 -0
- package/scripts/memalign-recall.js +111 -0
- package/scripts/memory-firewall.js +222 -0
- package/scripts/memory-migration.js +296 -0
- package/scripts/meta-policy.js +190 -0
- package/scripts/metered-billing.js +16 -0
- package/scripts/model-tier-router.js +301 -0
- package/scripts/money-watcher.js +71 -0
- package/scripts/multi-hop-recall.js +240 -0
- package/scripts/natural-language-harness.js +330 -0
- package/scripts/obsidian-export.js +713 -0
- package/scripts/operational-dashboard.js +103 -0
- package/scripts/operational-summary.js +93 -0
- package/scripts/optimize-context.js +17 -0
- package/scripts/org-dashboard.js +201 -0
- package/scripts/partner-orchestration.js +146 -0
- package/scripts/per-step-scoring.js +165 -0
- package/scripts/perplexity-marketing.js +466 -0
- package/scripts/pii-scanner.js +153 -0
- package/scripts/plan-gate.js +154 -0
- package/scripts/post-everywhere.js +308 -0
- package/scripts/post-to-x-retry.sh +22 -0
- package/scripts/post-to-x.js +369 -0
- package/scripts/pr-manager.js +236 -0
- package/scripts/predictive-insights.js +356 -0
- package/scripts/principle-extractor.js +162 -0
- package/scripts/pro-features.js +40 -0
- package/scripts/pro-local-dashboard.js +174 -0
- package/scripts/problem-detail.js +53 -0
- package/scripts/product-feedback.js +134 -0
- package/scripts/profile-router.js +245 -0
- package/scripts/prompt-dlp.js +221 -0
- package/scripts/prompt-guard.js +83 -0
- package/scripts/prove-adapters.js +863 -0
- package/scripts/prove-attribution.js +365 -0
- package/scripts/prove-automation.js +653 -0
- package/scripts/prove-autoresearch.js +304 -0
- package/scripts/prove-claim-verification.js +277 -0
- package/scripts/prove-cloudflare-sandbox.js +163 -0
- package/scripts/prove-data-pipeline.js +410 -0
- package/scripts/prove-data-quality.js +227 -0
- package/scripts/prove-evolution.js +352 -0
- package/scripts/prove-harnesses.js +287 -0
- package/scripts/prove-intelligence.js +259 -0
- package/scripts/prove-lancedb.js +371 -0
- package/scripts/prove-local-intelligence.js +342 -0
- package/scripts/prove-loop-closure.js +263 -0
- package/scripts/prove-predictive-insights.js +357 -0
- package/scripts/prove-runtime.js +350 -0
- package/scripts/prove-seo-gsd.js +234 -0
- package/scripts/prove-settings.js +279 -0
- package/scripts/prove-subway-upgrades.js +277 -0
- package/scripts/prove-tessl.js +229 -0
- package/scripts/prove-training-export.js +327 -0
- package/scripts/prove-workflow-contract.js +116 -0
- package/scripts/prove-xmemory.js +332 -0
- package/scripts/publish-decision.js +133 -0
- package/scripts/pulse.js +80 -0
- package/scripts/rate-limiter.js +125 -0
- package/scripts/reddit-dm-outreach.js +182 -0
- package/scripts/reddit-monitor-cron.sh +26 -0
- package/scripts/reflector-agent.js +221 -0
- package/scripts/reminder-engine.js +132 -0
- package/scripts/revenue-status.js +472 -0
- package/scripts/risk-scorer.js +459 -0
- package/scripts/rlaif-self-audit.js +129 -0
- package/scripts/rlhf_session_start.sh +32 -0
- package/scripts/rubric-engine.js +230 -0
- package/scripts/schedule-manager.js +251 -0
- package/scripts/secret-scanner.js +414 -0
- package/scripts/self-heal.js +147 -0
- package/scripts/self-healing-check.js +188 -0
- package/scripts/semantic-layer.js +98 -0
- package/scripts/seo-gsd.js +1153 -0
- package/scripts/settings-hierarchy.js +214 -0
- package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
- package/scripts/skill-exporter.js +262 -0
- package/scripts/skill-generator.js +446 -0
- package/scripts/skill-materializer.js +134 -0
- package/scripts/skill-packs.js +136 -0
- package/scripts/skill-proposer.js +99 -0
- package/scripts/skill-quality-tracker.js +282 -0
- package/scripts/slo-alert-engine.js +14 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/social-analytics/db/schema.sql +32 -0
- package/scripts/social-analytics/db/social-analytics.db +0 -0
- package/scripts/social-analytics/digest.js +256 -0
- package/scripts/social-analytics/generate-instagram-card.js +97 -0
- package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
- package/scripts/social-analytics/load-env.js +46 -0
- package/scripts/social-analytics/mcp-server.js +289 -0
- package/scripts/social-analytics/normalizer.js +580 -0
- package/scripts/social-analytics/notify.js +162 -0
- package/scripts/social-analytics/poll-all.js +92 -0
- package/scripts/social-analytics/pollers/github.js +195 -0
- package/scripts/social-analytics/pollers/instagram.js +253 -0
- package/scripts/social-analytics/pollers/linkedin.js +330 -0
- package/scripts/social-analytics/pollers/plausible.js +247 -0
- package/scripts/social-analytics/pollers/reddit.js +306 -0
- package/scripts/social-analytics/pollers/threads.js +233 -0
- package/scripts/social-analytics/pollers/tiktok.js +203 -0
- package/scripts/social-analytics/pollers/x.js +227 -0
- package/scripts/social-analytics/pollers/youtube.js +304 -0
- package/scripts/social-analytics/pollers/zernio.js +183 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
- package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
- package/scripts/social-analytics/publishers/devto.js +122 -0
- package/scripts/social-analytics/publishers/instagram.js +317 -0
- package/scripts/social-analytics/publishers/linkedin.js +294 -0
- package/scripts/social-analytics/publishers/reddit.js +390 -0
- package/scripts/social-analytics/publishers/threads.js +275 -0
- package/scripts/social-analytics/publishers/tiktok.js +217 -0
- package/scripts/social-analytics/publishers/x.js +259 -0
- package/scripts/social-analytics/publishers/youtube.js +223 -0
- package/scripts/social-analytics/publishers/zernio.js +378 -0
- package/scripts/social-analytics/run-digest.js +34 -0
- package/scripts/social-analytics/store.js +257 -0
- package/scripts/social-analytics/utm.js +143 -0
- package/scripts/social-pipeline.js +2628 -0
- package/scripts/social-quality-gate.js +18 -0
- package/scripts/social-reply-monitor.js +445 -0
- package/scripts/status-dashboard.js +155 -0
- package/scripts/statusline-lesson.js +16 -0
- package/scripts/statusline-tower.js +8 -0
- package/scripts/statusline.sh +116 -0
- package/scripts/stripe-live-status.js +115 -0
- package/scripts/subagent-profiles.js +79 -0
- package/scripts/sync-gh-secrets-from-env.sh +70 -0
- package/scripts/sync-github-about.js +52 -0
- package/scripts/sync-version.js +447 -0
- package/scripts/synthetic-dpo.js +234 -0
- package/scripts/telemetry-analytics.js +821 -0
- package/scripts/tessl-export.js +371 -0
- package/scripts/test-coverage.js +120 -0
- package/scripts/thompson-sampling.js +417 -0
- package/scripts/thumbgate-search.js +189 -0
- package/scripts/tool-kpi-tracker.js +12 -0
- package/scripts/tool-registry.js +811 -0
- package/scripts/train_from_feedback.py +933 -0
- package/scripts/user-profile.js +78 -0
- package/scripts/validate-feedback.js +581 -0
- package/scripts/validate-workflow-contract.js +287 -0
- package/scripts/vector-store.js +197 -0
- package/scripts/verification-loop.js +291 -0
- package/scripts/verify-obsidian-setup.sh +269 -0
- package/scripts/verify-run.js +269 -0
- package/scripts/webhook-delivery.js +62 -0
- package/scripts/weekly-auto-post.js +124 -0
- package/scripts/workflow-runs.js +154 -0
- package/scripts/workflow-sprint-intake.js +475 -0
- package/scripts/workspace-evolver.js +374 -0
- package/scripts/x-autonomous-marketing.js +139 -0
- package/scripts/xmemory-lite.js +405 -0
- package/skills/agent-memory/SKILL.md +97 -0
- package/skills/rlhf-feedback/SKILL.md +49 -0
- package/skills/solve-architecture-autonomy/SKILL.md +17 -0
- package/skills/solve-architecture-autonomy/tool.js +33 -0
- package/skills/thumbgate/SKILL.md +114 -0
- package/src/api/server.js +4206 -0
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
captureFeedback,
|
|
9
|
+
feedbackSummary,
|
|
10
|
+
analyzeFeedback,
|
|
11
|
+
writePreventionRules,
|
|
12
|
+
listEnforcementMatrix,
|
|
13
|
+
FEEDBACK_LOG_PATH,
|
|
14
|
+
readJSONL,
|
|
15
|
+
getFeedbackPaths,
|
|
16
|
+
} = require('../../scripts/feedback-loop');
|
|
17
|
+
const {
|
|
18
|
+
ensureContextFs,
|
|
19
|
+
normalizeNamespaces,
|
|
20
|
+
constructContextPack,
|
|
21
|
+
evaluateContextPack,
|
|
22
|
+
getProvenance,
|
|
23
|
+
writeSessionHandoff,
|
|
24
|
+
readSessionHandoff,
|
|
25
|
+
} = require('../../scripts/contextfs');
|
|
26
|
+
const { buildRubricEvaluation } = require('../../scripts/rubric-engine');
|
|
27
|
+
const {
|
|
28
|
+
listIntents,
|
|
29
|
+
planIntent,
|
|
30
|
+
} = require('../../scripts/intent-router');
|
|
31
|
+
const {
|
|
32
|
+
startHandoff,
|
|
33
|
+
completeHandoff,
|
|
34
|
+
} = require('../../scripts/delegation-runtime');
|
|
35
|
+
const {
|
|
36
|
+
getActiveMcpProfile,
|
|
37
|
+
getAllowedTools,
|
|
38
|
+
assertToolAllowed,
|
|
39
|
+
} = require('../../scripts/mcp-policy');
|
|
40
|
+
const {
|
|
41
|
+
evaluateGates,
|
|
42
|
+
evaluateGatesAsync,
|
|
43
|
+
evaluateSecretGuard,
|
|
44
|
+
satisfyCondition,
|
|
45
|
+
loadStats: loadGateStats,
|
|
46
|
+
trackAction,
|
|
47
|
+
verifyClaimEvidence,
|
|
48
|
+
registerClaimGate,
|
|
49
|
+
} = require('../../scripts/gates-engine');
|
|
50
|
+
const { diagnoseFailure } = require('../../scripts/failure-diagnostics');
|
|
51
|
+
const {
|
|
52
|
+
analyzeCodeGraphImpact,
|
|
53
|
+
formatCodeGraphRecallSection,
|
|
54
|
+
} = require('../../scripts/codegraph-context');
|
|
55
|
+
const {
|
|
56
|
+
exportDpoFromMemories,
|
|
57
|
+
DEFAULT_LOCAL_MEMORY_LOG,
|
|
58
|
+
} = require('../../scripts/export-dpo-pairs');
|
|
59
|
+
const { exportDatabricksBundle } = require('../../scripts/export-databricks-bundle');
|
|
60
|
+
const { generateDashboard } = require('../../scripts/dashboard');
|
|
61
|
+
const { getSettingsStatus } = require('../../scripts/settings-hierarchy');
|
|
62
|
+
const { generateSkills } = require('../../scripts/skill-generator');
|
|
63
|
+
const {
|
|
64
|
+
loadModel,
|
|
65
|
+
getReliability,
|
|
66
|
+
} = require('../../scripts/thompson-sampling');
|
|
67
|
+
const {
|
|
68
|
+
searchLessons,
|
|
69
|
+
} = require('../../scripts/lesson-search');
|
|
70
|
+
const {
|
|
71
|
+
retrieveRelevantLessons,
|
|
72
|
+
} = require('../../scripts/lesson-retrieval');
|
|
73
|
+
const {
|
|
74
|
+
searchRlhf,
|
|
75
|
+
} = require('../../scripts/thumbgate-search');
|
|
76
|
+
const { checkLimit, UPGRADE_MESSAGE } = require('../../scripts/rate-limiter');
|
|
77
|
+
const { generateOrgDashboard } = require('../../scripts/org-dashboard');
|
|
78
|
+
const {
|
|
79
|
+
listHarnesses,
|
|
80
|
+
runHarness,
|
|
81
|
+
} = require('../../scripts/natural-language-harness');
|
|
82
|
+
const { TOOLS } = require('../../scripts/tool-registry');
|
|
83
|
+
const { reflect: reflectOnFeedback } = require('../../scripts/reflector-agent');
|
|
84
|
+
const { submitProductIssue } = require('../../scripts/product-feedback');
|
|
85
|
+
|
|
86
|
+
const PRO_CHECKOUT_URL = 'https://thumbgate-production.up.railway.app/checkout/pro';
|
|
87
|
+
|
|
88
|
+
function enforceLimit(action) {
|
|
89
|
+
const limit = checkLimit(action);
|
|
90
|
+
if (!limit.allowed) {
|
|
91
|
+
const err = new Error(
|
|
92
|
+
`Free tier daily limit reached for "${action}". ${UPGRADE_MESSAGE}\nUpgrade now: ${PRO_CHECKOUT_URL}`
|
|
93
|
+
);
|
|
94
|
+
err.errorCategory = 'rate_limit';
|
|
95
|
+
err.isRetryable = false;
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const { bootstrapInternalAgent } = require('../../scripts/internal-agent-bootstrap');
|
|
100
|
+
const {
|
|
101
|
+
openSession: openFeedbackSession,
|
|
102
|
+
appendToSession: appendFeedbackContext,
|
|
103
|
+
finalizeSession: finalizeFeedbackSession,
|
|
104
|
+
} = require('../../scripts/feedback-session');
|
|
105
|
+
|
|
106
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '0.9.9' };
|
|
107
|
+
const COMMERCE_CATEGORIES = [
|
|
108
|
+
'product_recommendation',
|
|
109
|
+
'brand_compliance',
|
|
110
|
+
'sizing',
|
|
111
|
+
'pricing',
|
|
112
|
+
'regulatory',
|
|
113
|
+
];
|
|
114
|
+
const SAFE_DATA_DIR = path.resolve(path.dirname(FEEDBACK_LOG_PATH));
|
|
115
|
+
|
|
116
|
+
function resolveSafePath(targetPath, { mustExist = false } = {}) {
|
|
117
|
+
const baseDir = SAFE_DATA_DIR;
|
|
118
|
+
const resolved = path.resolve(baseDir, String(targetPath || ''));
|
|
119
|
+
const relative = path.relative(baseDir, resolved);
|
|
120
|
+
|
|
121
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
122
|
+
throw new Error(`Path must stay within ${baseDir}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (mustExist && !fs.existsSync(resolved)) {
|
|
126
|
+
throw new Error(`Path does not exist: ${resolved}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return resolved;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function toTextResult(payload) {
|
|
133
|
+
const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: 'text', text }],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function formatContextPack(pack) {
|
|
140
|
+
const lines = [
|
|
141
|
+
'## Context Pack',
|
|
142
|
+
'',
|
|
143
|
+
`Pack ID: ${pack.packId}`,
|
|
144
|
+
`Items: ${Array.isArray(pack.items) ? pack.items.length : 0}`,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const visibleTitles = pack.visibility && Array.isArray(pack.visibility.visibleTitles)
|
|
148
|
+
? pack.visibility.visibleTitles
|
|
149
|
+
: [];
|
|
150
|
+
if (visibleTitles.length > 0) {
|
|
151
|
+
lines.push(`Visible titles: ${visibleTitles.join(' | ')}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const item of (pack.items || []).slice(0, 5)) {
|
|
155
|
+
lines.push(`- [${item.namespace}] ${item.title} (score ${item.score})`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildRecallResponse(args = {}) {
|
|
162
|
+
const limit = checkLimit('recall');
|
|
163
|
+
ensureContextFs();
|
|
164
|
+
const pack = constructContextPack({
|
|
165
|
+
query: args.query || '',
|
|
166
|
+
maxItems: Number(args.limit || 5),
|
|
167
|
+
});
|
|
168
|
+
const impact = analyzeCodeGraphImpact({
|
|
169
|
+
intentId: null,
|
|
170
|
+
context: args.query || '',
|
|
171
|
+
repoPath: args.repoPath,
|
|
172
|
+
});
|
|
173
|
+
const section = formatCodeGraphRecallSection(impact);
|
|
174
|
+
let text = section
|
|
175
|
+
? `${formatContextPack(pack)}\n\n${section}`
|
|
176
|
+
: formatContextPack(pack);
|
|
177
|
+
|
|
178
|
+
if (!limit.allowed) {
|
|
179
|
+
text += '\n\n---\n';
|
|
180
|
+
text += 'Upgrade to Context Gateway for unlimited recall, shared workflow memory, and hosted rollout.\n';
|
|
181
|
+
text += 'Hosted API: https://thumbgate-production.up.railway.app\n';
|
|
182
|
+
text += 'Pro pack: https://thumbgate-production.up.railway.app/checkout/pro';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return toTextResult(text);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildDiagnoseFailureResponse(args = {}) {
|
|
189
|
+
let intentPlan = null;
|
|
190
|
+
const requestedProfile = args.mcpProfile || getActiveMcpProfile();
|
|
191
|
+
|
|
192
|
+
if (args.intentId) {
|
|
193
|
+
try {
|
|
194
|
+
intentPlan = planIntent({
|
|
195
|
+
intentId: args.intentId,
|
|
196
|
+
context: args.context || '',
|
|
197
|
+
mcpProfile: requestedProfile,
|
|
198
|
+
approved: args.approved === true,
|
|
199
|
+
repoPath: args.repoPath,
|
|
200
|
+
});
|
|
201
|
+
} catch (_) {
|
|
202
|
+
intentPlan = null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const allowedToolNames = getAllowedTools(requestedProfile);
|
|
207
|
+
const result = diagnoseFailure({
|
|
208
|
+
step: args.step,
|
|
209
|
+
context: args.context || '',
|
|
210
|
+
toolName: args.toolName,
|
|
211
|
+
toolArgs: args.toolArgs,
|
|
212
|
+
output: args.output,
|
|
213
|
+
error: args.error,
|
|
214
|
+
exitCode: args.exitCode,
|
|
215
|
+
verification: args.verification,
|
|
216
|
+
guardrails: args.guardrails,
|
|
217
|
+
rubricScores: args.rubricScores,
|
|
218
|
+
intentPlan,
|
|
219
|
+
mcpProfile: requestedProfile,
|
|
220
|
+
allowedToolNames,
|
|
221
|
+
toolSchemas: TOOLS.filter((tool) => allowedToolNames.includes(tool.name)),
|
|
222
|
+
includeConstraints: true,
|
|
223
|
+
projectRoot: args.repoPath,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return toTextResult(result);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function buildContextPackResponse(args = {}) {
|
|
230
|
+
ensureContextFs();
|
|
231
|
+
const namespaces = normalizeNamespaces(Array.isArray(args.namespaces) ? args.namespaces : []);
|
|
232
|
+
const pack = constructContextPack({
|
|
233
|
+
query: args.query || '',
|
|
234
|
+
maxItems: Number(args.maxItems || 8),
|
|
235
|
+
maxChars: Number(args.maxChars || 6000),
|
|
236
|
+
namespaces,
|
|
237
|
+
});
|
|
238
|
+
return toTextResult(pack);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function buildContextEvaluationResponse(args = {}) {
|
|
242
|
+
if (!args.packId || !args.outcome) {
|
|
243
|
+
throw new Error('packId and outcome are required');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let rubricEvaluation = null;
|
|
247
|
+
if (args.rubricScores != null || args.guardrails != null) {
|
|
248
|
+
rubricEvaluation = buildRubricEvaluation({
|
|
249
|
+
rubricScores: args.rubricScores,
|
|
250
|
+
guardrails: args.guardrails,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const evaluation = evaluateContextPack({
|
|
255
|
+
packId: args.packId,
|
|
256
|
+
outcome: args.outcome,
|
|
257
|
+
signal: args.signal || null,
|
|
258
|
+
notes: args.notes || '',
|
|
259
|
+
rubricEvaluation,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return toTextResult(evaluation);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function buildExportDpoResponse(args = {}) {
|
|
266
|
+
let memories = [];
|
|
267
|
+
|
|
268
|
+
if (args.inputPath) {
|
|
269
|
+
const inputPath = resolveSafePath(args.inputPath, { mustExist: true });
|
|
270
|
+
const raw = fs.readFileSync(inputPath, 'utf-8');
|
|
271
|
+
const parsed = JSON.parse(raw);
|
|
272
|
+
memories = Array.isArray(parsed) ? parsed : parsed.memories || [];
|
|
273
|
+
} else {
|
|
274
|
+
const memoryLogPath = args.memoryLogPath
|
|
275
|
+
? resolveSafePath(args.memoryLogPath, { mustExist: true })
|
|
276
|
+
: DEFAULT_LOCAL_MEMORY_LOG;
|
|
277
|
+
memories = readJSONL(memoryLogPath);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const result = exportDpoFromMemories(memories);
|
|
281
|
+
if (args.outputPath) {
|
|
282
|
+
const outputPath = resolveSafePath(args.outputPath);
|
|
283
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
284
|
+
fs.writeFileSync(outputPath, result.jsonl);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return toTextResult({
|
|
288
|
+
pairs: result.pairs.length,
|
|
289
|
+
errors: result.errors.length,
|
|
290
|
+
learnings: result.learnings.length,
|
|
291
|
+
unpairedErrors: result.unpairedErrors.length,
|
|
292
|
+
unpairedLearnings: result.unpairedLearnings.length,
|
|
293
|
+
outputPath: args.outputPath ? resolveSafePath(args.outputPath) : null,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function buildCommerceRecallResponse(args = {}) {
|
|
298
|
+
const requestedCategories = Array.isArray(args.categories) && args.categories.length > 0
|
|
299
|
+
? args.categories
|
|
300
|
+
: COMMERCE_CATEGORIES;
|
|
301
|
+
const modelPath = path.join(SAFE_DATA_DIR, 'feedback_model.json');
|
|
302
|
+
const reliability = getReliability(loadModel(modelPath));
|
|
303
|
+
const lines = ['## Commerce Quality Scores', ''];
|
|
304
|
+
|
|
305
|
+
for (const category of requestedCategories) {
|
|
306
|
+
const stats = reliability[category];
|
|
307
|
+
if (!stats) continue;
|
|
308
|
+
const successRate = typeof stats.success_rate === 'number'
|
|
309
|
+
? `${(stats.success_rate * 100).toFixed(1)}%`
|
|
310
|
+
: 'n/a';
|
|
311
|
+
lines.push(`- ${category}: ${successRate} success rate over ${stats.total || 0} samples`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (lines.length === 2) {
|
|
315
|
+
lines.push('- No commerce quality scores recorded yet.');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push(`Query: ${args.query || ''}`);
|
|
320
|
+
return toTextResult(lines.join('\n'));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildEstimateUncertaintyResponse(args = {}) {
|
|
324
|
+
const tags = Array.isArray(args.tags) ? args.tags.map(String) : [];
|
|
325
|
+
const { MEMORY_LOG_PATH } = getFeedbackPaths();
|
|
326
|
+
const memories = readJSONL(MEMORY_LOG_PATH);
|
|
327
|
+
const matching = memories.filter((entry) => {
|
|
328
|
+
if (!tags.length) return Boolean(entry && entry.bayesian);
|
|
329
|
+
const entryTags = Array.isArray(entry && entry.tags) ? entry.tags : [];
|
|
330
|
+
return entry && entry.bayesian && entryTags.some((tag) => tags.includes(tag));
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const uncertainties = matching
|
|
334
|
+
.map((entry) => Number(entry.bayesian && entry.bayesian.uncertainty))
|
|
335
|
+
.filter((value) => Number.isFinite(value));
|
|
336
|
+
const averageUncertainty = uncertainties.length > 0
|
|
337
|
+
? Number((uncertainties.reduce((sum, value) => sum + value, 0) / uncertainties.length).toFixed(4))
|
|
338
|
+
: 0;
|
|
339
|
+
|
|
340
|
+
return toTextResult({
|
|
341
|
+
tags,
|
|
342
|
+
matches: matching.length,
|
|
343
|
+
averageUncertainty,
|
|
344
|
+
minUncertainty: uncertainties.length > 0 ? Math.min(...uncertainties) : 0,
|
|
345
|
+
maxUncertainty: uncertainties.length > 0 ? Math.max(...uncertainties) : 0,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function callTool(name, args = {}) {
|
|
350
|
+
assertToolAllowed(name, getActiveMcpProfile());
|
|
351
|
+
const firewallResult = (await evaluateGatesAsync(name, args)) || evaluateSecretGuard({ tool_name: name, tool_input: args });
|
|
352
|
+
if (firewallResult && firewallResult.decision === 'deny') {
|
|
353
|
+
const err = new Error(`Action blocked by Semantic Firewall: ${firewallResult.message}`);
|
|
354
|
+
err.errorCategory = 'permission';
|
|
355
|
+
err.isRetryable = false;
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
358
|
+
const startMs = Date.now();
|
|
359
|
+
const result = await callToolInner(name, args);
|
|
360
|
+
const latencyMs = Date.now() - startMs;
|
|
361
|
+
try {
|
|
362
|
+
const { recordAuditEvent } = require('../../scripts/audit-trail');
|
|
363
|
+
recordAuditEvent({
|
|
364
|
+
toolName: name,
|
|
365
|
+
toolInput: args,
|
|
366
|
+
decision: 'allow',
|
|
367
|
+
latencyMs,
|
|
368
|
+
source: 'tool-latency',
|
|
369
|
+
});
|
|
370
|
+
} catch { /* audit write failure must never break tool response */ }
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function callToolInner(name, args) {
|
|
375
|
+
// Semantic Aliases for high-level branding alignment
|
|
376
|
+
if (name === 'capture_memory_feedback') name = 'capture_feedback';
|
|
377
|
+
if (name === 'get_reliability_rules') name = 'prevention_rules';
|
|
378
|
+
if (name === 'describe_reliability_entity') name = 'describe_semantic_entity';
|
|
379
|
+
|
|
380
|
+
switch (name) {
|
|
381
|
+
case 'capture_feedback':
|
|
382
|
+
|
|
383
|
+
return toTextResult(captureFeedback(args));
|
|
384
|
+
case 'feedback_summary':
|
|
385
|
+
return toTextResult(feedbackSummary(Number(args.recent || 20)));
|
|
386
|
+
case 'search_lessons':
|
|
387
|
+
return toTextResult(searchLessons(args.query || '', {
|
|
388
|
+
limit: Number(args.limit || 10),
|
|
389
|
+
category: args.category,
|
|
390
|
+
tags: Array.isArray(args.tags) ? args.tags : [],
|
|
391
|
+
}));
|
|
392
|
+
case 'retrieve_lessons':
|
|
393
|
+
return toTextResult(retrieveRelevantLessons(
|
|
394
|
+
args.toolName,
|
|
395
|
+
args.actionContext || '',
|
|
396
|
+
{ maxResults: Number(args.maxResults || 5) },
|
|
397
|
+
));
|
|
398
|
+
case 'search_rlhf':
|
|
399
|
+
enforceLimit('search_rlhf');
|
|
400
|
+
return toTextResult(searchRlhf({
|
|
401
|
+
query: args.query,
|
|
402
|
+
limit: args.limit,
|
|
403
|
+
source: args.source,
|
|
404
|
+
signal: args.signal,
|
|
405
|
+
}));
|
|
406
|
+
case 'feedback_stats':
|
|
407
|
+
return toTextResult(analyzeFeedback());
|
|
408
|
+
case 'diagnose_failure':
|
|
409
|
+
return buildDiagnoseFailureResponse(args);
|
|
410
|
+
case 'reflect_on_feedback':
|
|
411
|
+
return toTextResult(reflectOnFeedback({
|
|
412
|
+
conversationWindow: args.conversationWindow || [],
|
|
413
|
+
context: args.context || '',
|
|
414
|
+
whatWentWrong: args.whatWentWrong || '',
|
|
415
|
+
structuredRule: null,
|
|
416
|
+
feedbackEvent: args.feedbackEventId ? { id: args.feedbackEventId } : null,
|
|
417
|
+
}));
|
|
418
|
+
case 'report_product_issue':
|
|
419
|
+
return toTextResult(await submitProductIssue({
|
|
420
|
+
title: args.title,
|
|
421
|
+
body: args.body,
|
|
422
|
+
category: args.category || 'bug',
|
|
423
|
+
source: 'mcp tool',
|
|
424
|
+
}));
|
|
425
|
+
case 'list_intents':
|
|
426
|
+
return toTextResult(listIntents({
|
|
427
|
+
mcpProfile: args.mcpProfile,
|
|
428
|
+
bundleId: args.bundleId,
|
|
429
|
+
partnerProfile: args.partnerProfile,
|
|
430
|
+
}));
|
|
431
|
+
case 'plan_intent':
|
|
432
|
+
return toTextResult(planIntent({
|
|
433
|
+
intentId: args.intentId,
|
|
434
|
+
context: args.context || '',
|
|
435
|
+
mcpProfile: args.mcpProfile,
|
|
436
|
+
bundleId: args.bundleId,
|
|
437
|
+
partnerProfile: args.partnerProfile,
|
|
438
|
+
delegationMode: args.delegationMode,
|
|
439
|
+
approved: args.approved === true,
|
|
440
|
+
repoPath: args.repoPath,
|
|
441
|
+
}));
|
|
442
|
+
case 'start_handoff':
|
|
443
|
+
return toTextResult(startHandoff({
|
|
444
|
+
plan: planIntent({
|
|
445
|
+
intentId: args.intentId,
|
|
446
|
+
context: args.context || '',
|
|
447
|
+
mcpProfile: args.mcpProfile,
|
|
448
|
+
bundleId: args.bundleId,
|
|
449
|
+
partnerProfile: args.partnerProfile,
|
|
450
|
+
delegationMode: 'sequential',
|
|
451
|
+
approved: args.approved === true,
|
|
452
|
+
repoPath: args.repoPath,
|
|
453
|
+
}),
|
|
454
|
+
context: args.context || '',
|
|
455
|
+
mcpProfile: args.mcpProfile || getActiveMcpProfile(),
|
|
456
|
+
partnerProfile: args.partnerProfile || null,
|
|
457
|
+
repoPath: args.repoPath,
|
|
458
|
+
delegateProfile: args.delegateProfile || null,
|
|
459
|
+
plannedChecks: Array.isArray(args.plannedChecks) ? args.plannedChecks : [],
|
|
460
|
+
}));
|
|
461
|
+
case 'complete_handoff':
|
|
462
|
+
return toTextResult(completeHandoff({
|
|
463
|
+
handoffId: args.handoffId,
|
|
464
|
+
outcome: args.outcome,
|
|
465
|
+
resultContext: args.resultContext || '',
|
|
466
|
+
attempts: args.attempts,
|
|
467
|
+
violationCount: args.violationCount,
|
|
468
|
+
tokenEstimate: args.tokenEstimate,
|
|
469
|
+
latencyMs: args.latencyMs,
|
|
470
|
+
summary: args.summary || '',
|
|
471
|
+
}));
|
|
472
|
+
case 'enforcement_matrix':
|
|
473
|
+
return toTextResult(listEnforcementMatrix());
|
|
474
|
+
case 'prevention_rules': {
|
|
475
|
+
const outputPath = args.outputPath ? resolveSafePath(args.outputPath) : undefined;
|
|
476
|
+
return toTextResult(writePreventionRules(outputPath, Number(args.minOccurrences || 2)));
|
|
477
|
+
}
|
|
478
|
+
case 'export_dpo_pairs':
|
|
479
|
+
enforceLimit('export_dpo');
|
|
480
|
+
return buildExportDpoResponse(args);
|
|
481
|
+
case 'export_databricks_bundle': {
|
|
482
|
+
enforceLimit('export_databricks');
|
|
483
|
+
const outputPath = args.outputPath ? resolveSafePath(args.outputPath) : undefined;
|
|
484
|
+
return toTextResult(exportDatabricksBundle(undefined, outputPath));
|
|
485
|
+
}
|
|
486
|
+
case 'construct_context_pack':
|
|
487
|
+
return buildContextPackResponse(args);
|
|
488
|
+
case 'evaluate_context_pack':
|
|
489
|
+
return buildContextEvaluationResponse(args);
|
|
490
|
+
case 'context_provenance':
|
|
491
|
+
return toTextResult({ events: getProvenance(Number(args.limit || 50)) });
|
|
492
|
+
case 'generate_skill':
|
|
493
|
+
return toTextResult({
|
|
494
|
+
skills: generateSkills({
|
|
495
|
+
minClusterSize: Number(args.minOccurrences || 3),
|
|
496
|
+
}).filter((entry) => {
|
|
497
|
+
if (!Array.isArray(args.tags) || args.tags.length === 0) return true;
|
|
498
|
+
return args.tags.some((tag) => entry.skillName.includes(String(tag)));
|
|
499
|
+
}),
|
|
500
|
+
});
|
|
501
|
+
case 'recall':
|
|
502
|
+
return buildRecallResponse(args);
|
|
503
|
+
case 'satisfy_gate': {
|
|
504
|
+
if (!args.gate) {
|
|
505
|
+
throw new Error('gate is required');
|
|
506
|
+
}
|
|
507
|
+
const entry = satisfyCondition(args.gate, args.evidence || '', args.structuredReasoning || null);
|
|
508
|
+
const result = { satisfied: true, gate: args.gate, ...entry };
|
|
509
|
+
// Log structured reasoning to audit trail for learning
|
|
510
|
+
if (args.structuredReasoning) {
|
|
511
|
+
recordAuditEvent({
|
|
512
|
+
toolName: 'satisfy_gate',
|
|
513
|
+
toolInput: { gate: args.gate },
|
|
514
|
+
decision: 'allow',
|
|
515
|
+
gateId: args.gate,
|
|
516
|
+
message: `Gate satisfied with structured reasoning: ${args.structuredReasoning.conclusion || 'no conclusion'}`,
|
|
517
|
+
source: 'structured-reasoning',
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return toTextResult(result);
|
|
521
|
+
}
|
|
522
|
+
case 'track_action': {
|
|
523
|
+
const entry = trackAction(args.actionId, args.metadata || {});
|
|
524
|
+
return toTextResult({
|
|
525
|
+
tracked: true,
|
|
526
|
+
actionId: args.actionId,
|
|
527
|
+
...entry,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
case 'verify_claim':
|
|
531
|
+
return toTextResult(verifyClaimEvidence(args.claim));
|
|
532
|
+
case 'register_claim_gate':
|
|
533
|
+
return toTextResult(registerClaimGate(args.claimPattern, args.requiredActions, args.message));
|
|
534
|
+
case 'gate_stats':
|
|
535
|
+
return toTextResult(loadGateStats());
|
|
536
|
+
case 'dashboard':
|
|
537
|
+
return toTextResult(generateDashboard(getFeedbackPaths().FEEDBACK_DIR));
|
|
538
|
+
case 'org_dashboard':
|
|
539
|
+
return toTextResult(generateOrgDashboard({ windowHours: Number(args.windowHours || 24) }));
|
|
540
|
+
case 'settings_status':
|
|
541
|
+
return toTextResult(getSettingsStatus());
|
|
542
|
+
case 'commerce_recall':
|
|
543
|
+
enforceLimit('commerce_recall');
|
|
544
|
+
return buildCommerceRecallResponse(args);
|
|
545
|
+
case 'get_business_metrics': {
|
|
546
|
+
const { getBusinessMetrics } = require('../../scripts/semantic-layer');
|
|
547
|
+
const metrics = await getBusinessMetrics(args);
|
|
548
|
+
return toTextResult(metrics);
|
|
549
|
+
}
|
|
550
|
+
case 'describe_semantic_entity': {
|
|
551
|
+
const { describeSemanticSchema } = require('../../scripts/semantic-layer');
|
|
552
|
+
const schema = describeSemanticSchema();
|
|
553
|
+
const entity = schema.entities[args.type] || schema.metrics[args.type];
|
|
554
|
+
if (!entity) {
|
|
555
|
+
throw new Error(`Unknown semantic entity: ${args.type}`);
|
|
556
|
+
}
|
|
557
|
+
return toTextResult(entity);
|
|
558
|
+
}
|
|
559
|
+
case 'estimate_uncertainty':
|
|
560
|
+
return buildEstimateUncertaintyResponse(args);
|
|
561
|
+
case 'bootstrap_internal_agent':
|
|
562
|
+
return toTextResult(bootstrapInternalAgent(args));
|
|
563
|
+
case 'session_handoff':
|
|
564
|
+
return toTextResult(writeSessionHandoff(args));
|
|
565
|
+
case 'session_primer': {
|
|
566
|
+
const primer = readSessionHandoff();
|
|
567
|
+
if (!primer) return toTextResult({ message: 'No session primer found. This is the first session.' });
|
|
568
|
+
return toTextResult(primer);
|
|
569
|
+
}
|
|
570
|
+
case 'list_harnesses':
|
|
571
|
+
return toTextResult({ harnesses: listHarnesses({ tag: args.tag }) });
|
|
572
|
+
case 'run_harness':
|
|
573
|
+
return toTextResult(runHarness(args.harness, args.inputs || {}, { jobId: args.jobId }));
|
|
574
|
+
case 'open_feedback_session':
|
|
575
|
+
return toTextResult(openFeedbackSession(args.feedbackEventId, args.signal, args.initialContext));
|
|
576
|
+
case 'append_feedback_context':
|
|
577
|
+
return toTextResult(appendFeedbackContext(args.sessionId, args.message, args.role));
|
|
578
|
+
case 'finalize_feedback_session':
|
|
579
|
+
return toTextResult(finalizeFeedbackSession(args.sessionId));
|
|
580
|
+
default:
|
|
581
|
+
throw new Error(`Unsupported tool: ${name}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function handleRequest(message) {
|
|
586
|
+
// Notifications have no id and expect no response
|
|
587
|
+
if (message.id === undefined || message.id === null) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
if (message.method === 'initialize') {
|
|
591
|
+
return {
|
|
592
|
+
protocolVersion: '2024-11-05',
|
|
593
|
+
capabilities: { tools: {} },
|
|
594
|
+
serverInfo: SERVER_INFO,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
if (message.method === 'ping') return {};
|
|
598
|
+
if (message.method === 'tools/list') return { tools: TOOLS };
|
|
599
|
+
if (message.method === 'tools/call') return callTool(message.params.name, message.params.arguments);
|
|
600
|
+
throw new Error(`Unsupported method: ${message.method}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function tryParseMessage(buffer) {
|
|
604
|
+
const source = buffer.toString('utf8');
|
|
605
|
+
|
|
606
|
+
const headerEnd = source.indexOf('\r\n\r\n');
|
|
607
|
+
if (headerEnd !== -1) {
|
|
608
|
+
const header = source.slice(0, headerEnd);
|
|
609
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
610
|
+
if (!match) {
|
|
611
|
+
throw new Error('Missing Content-Length header');
|
|
612
|
+
}
|
|
613
|
+
const length = Number(match[1]);
|
|
614
|
+
const bodyStart = headerEnd + 4;
|
|
615
|
+
if (buffer.length < bodyStart + length) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
let request;
|
|
619
|
+
try {
|
|
620
|
+
request = JSON.parse(buffer.slice(bodyStart, bodyStart + length).toString('utf8'));
|
|
621
|
+
} catch (err) {
|
|
622
|
+
err.transport = 'framed';
|
|
623
|
+
err.jsonrpcCode = -32700;
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
request,
|
|
628
|
+
remaining: buffer.slice(bodyStart + length),
|
|
629
|
+
transport: 'framed',
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const newlineIndex = source.indexOf('\n');
|
|
634
|
+
if (newlineIndex === -1) return null;
|
|
635
|
+
const line = source.slice(0, newlineIndex).trim();
|
|
636
|
+
if (!line) {
|
|
637
|
+
return {
|
|
638
|
+
request: null,
|
|
639
|
+
remaining: Buffer.from(source.slice(newlineIndex + 1)),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
let request;
|
|
643
|
+
try {
|
|
644
|
+
request = JSON.parse(line);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
err.transport = 'ndjson';
|
|
647
|
+
err.jsonrpcCode = -32603;
|
|
648
|
+
throw err;
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
request,
|
|
652
|
+
remaining: Buffer.from(source.slice(newlineIndex + 1)),
|
|
653
|
+
transport: 'ndjson',
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function writeResponse(id, payload, error = null) {
|
|
658
|
+
const body = JSON.stringify(error
|
|
659
|
+
? { jsonrpc: '2.0', id, error }
|
|
660
|
+
: { jsonrpc: '2.0', id, result: payload });
|
|
661
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n${body}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function writeNdjsonResponse(id, payload, error = null) {
|
|
665
|
+
const body = JSON.stringify(error
|
|
666
|
+
? { jsonrpc: '2.0', id, error }
|
|
667
|
+
: { jsonrpc: '2.0', id, result: payload });
|
|
668
|
+
process.stdout.write(`${body}\n`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Default staleness threshold: if a lock is older than this (ms), the holder
|
|
673
|
+
* is considered orphaned even if its PID is still alive — it likely belongs
|
|
674
|
+
* to a defunct Claude Code session whose process was never reaped.
|
|
675
|
+
*/
|
|
676
|
+
const LOCK_STALE_MS = Number(process.env.THUMBGATE_LOCK_STALE_MS) || 2 * 60 * 60 * 1000; // 2 hours
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Acquire a file-system lock to prevent duplicate MCP server instances.
|
|
680
|
+
* Returns { lockFile, cleanupLock } on success, or calls process.exit(1)
|
|
681
|
+
* if another live server holds the lock.
|
|
682
|
+
*
|
|
683
|
+
* Staleness reaping: if the lock-holding process is alive but the lock is
|
|
684
|
+
* older than LOCK_STALE_MS, the holder is killed (SIGTERM) and the lock is
|
|
685
|
+
* reclaimed. This prevents orphaned `rlhf serve` processes from permanently
|
|
686
|
+
* blocking new sessions.
|
|
687
|
+
*/
|
|
688
|
+
function acquireLock() {
|
|
689
|
+
const feedbackDir = getFeedbackPaths().FEEDBACK_DIR;
|
|
690
|
+
const lockFile = path.join(feedbackDir, '.mcp-server.lock');
|
|
691
|
+
try {
|
|
692
|
+
fs.mkdirSync(feedbackDir, { recursive: true });
|
|
693
|
+
if (fs.existsSync(lockFile)) {
|
|
694
|
+
const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
695
|
+
let isRunning = false;
|
|
696
|
+
try { process.kill(lockData.pid, 0); isRunning = true; } catch { /* process is dead */ }
|
|
697
|
+
|
|
698
|
+
if (isRunning) {
|
|
699
|
+
const lockAge = Date.now() - new Date(lockData.startedAt).getTime();
|
|
700
|
+
if (lockAge > LOCK_STALE_MS) {
|
|
701
|
+
// Orphaned process — kill it and take over
|
|
702
|
+
process.stderr.write(`[thumbgate] Lock held by PID ${lockData.pid} is ${Math.round(lockAge / 60000)}m old (threshold: ${Math.round(LOCK_STALE_MS / 60000)}m). Reaping orphaned process.\n`);
|
|
703
|
+
try { process.kill(lockData.pid, 'SIGTERM'); } catch { /* already gone */ }
|
|
704
|
+
} else {
|
|
705
|
+
process.stderr.write(`[thumbgate] FATAL: another MCP server (PID ${lockData.pid}) is already serving ${feedbackDir}. Refusing to start — would cause SQLite lock contention.\n`);
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// Stale lock from a dead or reaped process — remove it
|
|
710
|
+
try { fs.unlinkSync(lockFile); } catch { /* already gone */ }
|
|
711
|
+
process.stderr.write(`[thumbgate] Removed stale lock (PID ${lockData.pid} is no longer running).\n`);
|
|
712
|
+
}
|
|
713
|
+
fs.writeFileSync(lockFile, JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
|
|
714
|
+
const cleanupLock = () => { try { fs.unlinkSync(lockFile); } catch { /* already removed */ } };
|
|
715
|
+
process.on('exit', cleanupLock);
|
|
716
|
+
process.on('SIGTERM', () => { cleanupLock(); process.exit(0); });
|
|
717
|
+
process.on('SIGINT', () => { cleanupLock(); process.exit(0); });
|
|
718
|
+
return { lockFile, cleanupLock };
|
|
719
|
+
} catch { /* best-effort lock */ }
|
|
720
|
+
return { lockFile, cleanupLock: () => {} };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function startStdioServer() {
|
|
724
|
+
acquireLock();
|
|
725
|
+
|
|
726
|
+
process.stdin.resume();
|
|
727
|
+
let buffer = Buffer.alloc(0);
|
|
728
|
+
// Auto-detect transport from first request and lock it for the session.
|
|
729
|
+
// mcp-proxy (Glama) sends NDJSON and expects NDJSON back.
|
|
730
|
+
let sessionTransport = process.env.MCP_TRANSPORT || null;
|
|
731
|
+
|
|
732
|
+
process.stdin.on('data', async (chunk) => {
|
|
733
|
+
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
734
|
+
|
|
735
|
+
while (buffer.length > 0) {
|
|
736
|
+
let parsed;
|
|
737
|
+
try {
|
|
738
|
+
parsed = tryParseMessage(buffer);
|
|
739
|
+
} catch (err) {
|
|
740
|
+
const error = {
|
|
741
|
+
code: err.jsonrpcCode || -32700,
|
|
742
|
+
message: err.message,
|
|
743
|
+
};
|
|
744
|
+
if (err.transport === 'ndjson' || sessionTransport === 'ndjson') {
|
|
745
|
+
writeNdjsonResponse(null, null, error);
|
|
746
|
+
} else {
|
|
747
|
+
writeResponse(null, null, error);
|
|
748
|
+
}
|
|
749
|
+
buffer = Buffer.alloc(0);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (!parsed) return;
|
|
754
|
+
buffer = parsed.remaining;
|
|
755
|
+
if (!parsed.request) continue;
|
|
756
|
+
|
|
757
|
+
// Lock transport on first successful parse
|
|
758
|
+
if (!sessionTransport && parsed.transport) {
|
|
759
|
+
sessionTransport = parsed.transport;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const respond = sessionTransport === 'ndjson' ? writeNdjsonResponse : writeResponse;
|
|
763
|
+
|
|
764
|
+
try {
|
|
765
|
+
const result = await handleRequest(parsed.request);
|
|
766
|
+
if (result !== null) {
|
|
767
|
+
respond(parsed.request.id ?? null, result);
|
|
768
|
+
}
|
|
769
|
+
} catch (err) {
|
|
770
|
+
respond(parsed.request.id ?? null, null, {
|
|
771
|
+
code: -32603,
|
|
772
|
+
message: err.message,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (require.main === module) startStdioServer();
|
|
780
|
+
|
|
781
|
+
module.exports = {
|
|
782
|
+
TOOLS,
|
|
783
|
+
SAFE_DATA_DIR,
|
|
784
|
+
handleRequest,
|
|
785
|
+
callTool,
|
|
786
|
+
startStdioServer,
|
|
787
|
+
acquireLock,
|
|
788
|
+
};
|