thumbgate 1.27.18 → 1.27.20
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/commands/dashboard.md +15 -0
- package/.claude/commands/thumbgate-blocked.md +27 -0
- package/.claude/commands/thumbgate-dashboard.md +15 -0
- package/.claude/commands/thumbgate-doctor.md +30 -0
- package/.claude/commands/thumbgate-guard.md +36 -0
- package/.claude/commands/thumbgate-protect.md +30 -0
- package/.claude/commands/thumbgate-rules.md +30 -0
- package/.claude-plugin/plugin.json +4 -3
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +33 -12
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +246 -30
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/gemini/function-declarations.json +1 -0
- package/adapters/letta/README.md +41 -0
- package/adapters/letta/thumbgate-letta-adapter.js +133 -0
- package/adapters/mcp/server-stdio.js +263 -11
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bench/observability-eval-suite.json +26 -0
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +1456 -122
- package/bin/dashboard-cli.js +7 -0
- package/bin/postinstall.js +11 -27
- package/commands/dashboard.md +15 -0
- package/commands/thumbgate-dashboard.md +15 -0
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +216 -0
- package/config/gates/claim-verification.json +12 -0
- package/config/gates/default.json +31 -2
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +23 -13
- package/config/merge-quality-checks.json +0 -1
- package/config/model-candidates.json +121 -6
- package/config/post-deploy-marketing-pages.json +80 -0
- package/config/tessl-tiles.json +1 -3
- package/openapi/openapi.yaml +12 -0
- package/package.json +225 -100
- package/public/about.html +162 -0
- package/public/agent-manager.html +179 -0
- package/public/agents-cost-savings.html +153 -0
- package/public/ai-malpractice-prevention.html +818 -0
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +28 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +18 -0
- package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
- package/public/assets/brand/thumbgate-mark-team.svg +26 -0
- package/public/assets/brand/thumbgate-mark.svg +21 -0
- package/public/assets/brand/thumbgate-wordmark.svg +20 -0
- package/public/assets/claude-thumbgate-statusbar.svg +8 -0
- package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
- package/public/assets/legal-intake-control-flow.svg +66 -0
- package/public/blog.html +4 -4
- package/public/brand/thumbgate-mark.svg +19 -0
- package/public/brand/thumbgate-og.svg +16 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-enterprise.html +123 -0
- package/public/codex-plugin.html +72 -20
- package/public/compare.html +31 -8
- package/public/dashboard.html +930 -166
- package/public/diagnostic.html +345 -0
- package/public/federal.html +2 -2
- package/public/guide.html +33 -13
- package/public/index.html +469 -111
- package/public/install.html +193 -0
- package/public/js/buyer-intent.js +672 -0
- package/public/learn.html +183 -18
- package/public/lessons.html +168 -10
- package/public/numbers.html +7 -7
- package/public/pricing.html +399 -0
- package/public/pro.html +34 -11
- package/scripts/action-receipts.js +324 -0
- package/scripts/activation-quickstart.js +187 -0
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/agent-readiness.js +20 -3
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/async-eval-observability.js +236 -0
- package/scripts/audit.js +65 -0
- package/scripts/auto-promote-gates.js +82 -10
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/aws-blocks-guardrails.js +272 -0
- package/scripts/billing.js +93 -1
- package/scripts/bot-detection.js +61 -3
- package/scripts/build-metadata.js +50 -10
- package/scripts/classifier-routing.js +130 -0
- package/scripts/cli-feedback.js +4 -2
- package/scripts/cli-schema.js +97 -0
- package/scripts/cli-telemetry.js +6 -1
- package/scripts/commercial-offer.js +82 -2
- package/scripts/context-manager.js +74 -6
- package/scripts/dashboard-chat.js +332 -0
- package/scripts/dashboard.js +68 -2
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-aggregate.js +281 -0
- package/scripts/feedback-loop.js +123 -1
- package/scripts/feedback-quality.js +87 -0
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +1176 -85
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-runtime.js +20 -14
- package/scripts/hook-stop-anti-claim.js +301 -0
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/hybrid-feedback-context.js +142 -7
- package/scripts/install-shim.js +87 -0
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/license.js +10 -10
- package/scripts/llm-client.js +169 -4
- package/scripts/local-model-profile.js +15 -8
- package/scripts/mcp-config.js +7 -1
- package/scripts/mcp-oauth.js +293 -0
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/meta-agent-loop.js +36 -0
- package/scripts/noop-detect.js +285 -0
- package/scripts/operational-integrity.js +39 -5
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plan-gate.js +243 -0
- package/scripts/plausible-domain-config.js +99 -0
- package/scripts/plausible-server-events.js +9 -6
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/published-cli.js +0 -8
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/rate-limiter.js +64 -13
- package/scripts/repeat-metric.js +137 -0
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-redaction.js +166 -0
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +260 -10
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/self-harness-optimizer.js +141 -0
- package/scripts/self-healing-check.js +193 -0
- package/scripts/self-protection.js +90 -0
- package/scripts/seo-gsd.js +916 -7
- package/scripts/silent-failure-cluster.js +531 -0
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-cache-read.js +57 -0
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +28 -2
- package/scripts/statusline.sh +20 -4
- package/scripts/sync-telemetry-from-prod.js +374 -0
- package/scripts/telemetry-analytics.js +357 -0
- package/scripts/thompson-sampling.js +31 -10
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-contract-validator.js +76 -0
- package/scripts/tool-registry.js +169 -1
- package/scripts/trajectory-scorer.js +63 -0
- package/scripts/vector-store.js +45 -0
- package/scripts/verify-marketing-pages-deployed.js +212 -0
- package/scripts/visitor-journey.js +172 -0
- package/scripts/workflow-sentinel.js +286 -53
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +2683 -319
- package/.claude-plugin/marketplace.json +0 -85
- package/adapters/chatgpt/openapi.yaml +0 -1695
- package/scripts/bot-detector.js +0 -50
|
@@ -70,6 +70,21 @@ const {
|
|
|
70
70
|
verifyClaimEvidence,
|
|
71
71
|
registerClaimGate,
|
|
72
72
|
} = require('../../scripts/gates-engine');
|
|
73
|
+
const { mergeRepeatMetricIntoGateStats } = require('../../scripts/repeat-metric');
|
|
74
|
+
const {
|
|
75
|
+
detectNoop,
|
|
76
|
+
computeActionStateHash,
|
|
77
|
+
recordActionAttempt,
|
|
78
|
+
isRepeatAttempt,
|
|
79
|
+
} = require('../../scripts/noop-detect');
|
|
80
|
+
const { recordAuditEvent } = require('../../scripts/audit-trail');
|
|
81
|
+
const {
|
|
82
|
+
recordReceipt,
|
|
83
|
+
getReceiptForAction,
|
|
84
|
+
getRecentReceipts,
|
|
85
|
+
pairFeedbackWithReceipt,
|
|
86
|
+
buildReceiptContextEntries,
|
|
87
|
+
} = require('../../scripts/action-receipts');
|
|
73
88
|
const {
|
|
74
89
|
evaluateOperationalIntegrity,
|
|
75
90
|
} = require('../../scripts/operational-integrity');
|
|
@@ -216,7 +231,7 @@ const {
|
|
|
216
231
|
finalizeSession: finalizeFeedbackSession,
|
|
217
232
|
} = require('../../scripts/feedback-session');
|
|
218
233
|
|
|
219
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.20
|
|
234
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.20' };
|
|
220
235
|
const COMMERCE_CATEGORIES = [
|
|
221
236
|
'product_recommendation',
|
|
222
237
|
'brand_compliance',
|
|
@@ -368,6 +383,111 @@ function buildRecallResponse(args = {}) {
|
|
|
368
383
|
return toTextResult(text);
|
|
369
384
|
}
|
|
370
385
|
|
|
386
|
+
function buildSuggestFixResponse(args = {}) {
|
|
387
|
+
const context = String(args.context || '').trim();
|
|
388
|
+
const rawLimit = Number(args.limit);
|
|
389
|
+
const limit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, 5) : 3;
|
|
390
|
+
|
|
391
|
+
// If no context provided, return generic suggestion
|
|
392
|
+
if (!context) {
|
|
393
|
+
return toTextResult({
|
|
394
|
+
suggestions: [
|
|
395
|
+
{
|
|
396
|
+
action: 'Capture feedback about what went wrong so ThumbGate can learn and prevent recurrence.',
|
|
397
|
+
source: 'generic',
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
query: '',
|
|
401
|
+
totalFound: 0,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Search lessons via lesson-search module
|
|
406
|
+
const lessonModule = loadPrivateMcpModule('lessonSearch');
|
|
407
|
+
let lessonActions = [];
|
|
408
|
+
if (lessonModule) {
|
|
409
|
+
try {
|
|
410
|
+
const searchResult = lessonModule.searchLessons(context, { limit: 10 });
|
|
411
|
+
const results = Array.isArray(searchResult && searchResult.results) ? searchResult.results : [];
|
|
412
|
+
for (const result of results) {
|
|
413
|
+
const correctiveActions = (result.systemResponse && Array.isArray(result.systemResponse.correctiveActions))
|
|
414
|
+
? result.systemResponse.correctiveActions
|
|
415
|
+
: [];
|
|
416
|
+
for (const action of correctiveActions) {
|
|
417
|
+
const text = String(action.text || '').trim();
|
|
418
|
+
if (text) {
|
|
419
|
+
lessonActions.push({
|
|
420
|
+
action: text,
|
|
421
|
+
source: action.source || `lesson:${result.id || 'unknown'}`,
|
|
422
|
+
score: result.score || 0,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Also pick up lesson-level howToAvoid / actionNeeded when no explicit correctiveActions
|
|
427
|
+
if (correctiveActions.length === 0 && result.lesson) {
|
|
428
|
+
const text = result.lesson.howToAvoid || result.lesson.actionNeeded || '';
|
|
429
|
+
if (text) {
|
|
430
|
+
lessonActions.push({
|
|
431
|
+
action: String(text).trim(),
|
|
432
|
+
source: `lesson:${result.id || 'unknown'}`,
|
|
433
|
+
score: result.score || 0,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch {
|
|
439
|
+
// lesson search failure is non-fatal
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Search prevention rules directly via lesson-search module's helper
|
|
444
|
+
let ruleActions = [];
|
|
445
|
+
try {
|
|
446
|
+
const { readPreventionRuleMatches } = require('../../scripts/lesson-search');
|
|
447
|
+
const ruleMatches = readPreventionRuleMatches(context, limit);
|
|
448
|
+
for (const rule of ruleMatches) {
|
|
449
|
+
const text = rule.summary || rule.title || '';
|
|
450
|
+
if (text) {
|
|
451
|
+
ruleActions.push({
|
|
452
|
+
action: String(text).trim(),
|
|
453
|
+
source: `rule:${String(rule.title || 'prevention_rules').trim()}`,
|
|
454
|
+
score: rule.score || 0,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} catch {
|
|
459
|
+
// rule search failure is non-fatal
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Merge, deduplicate, sort by score, and take top `limit`
|
|
463
|
+
const seen = new Set();
|
|
464
|
+
const all = [...lessonActions, ...ruleActions]
|
|
465
|
+
.filter((item) => {
|
|
466
|
+
if (!item.action) return false;
|
|
467
|
+
const key = item.action.toLowerCase();
|
|
468
|
+
if (seen.has(key)) return false;
|
|
469
|
+
seen.add(key);
|
|
470
|
+
return true;
|
|
471
|
+
})
|
|
472
|
+
.sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
473
|
+
.slice(0, limit)
|
|
474
|
+
.map(({ action, source }) => ({ action, source }));
|
|
475
|
+
|
|
476
|
+
// If nothing matched, add generic fallback
|
|
477
|
+
if (all.length === 0) {
|
|
478
|
+
all.push({
|
|
479
|
+
action: 'No matching lessons or rules found. Capture feedback via capture_feedback so ThumbGate can learn from this failure.',
|
|
480
|
+
source: 'generic',
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return toTextResult({
|
|
485
|
+
suggestions: all,
|
|
486
|
+
query: context,
|
|
487
|
+
totalFound: all.length,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
371
491
|
function buildDiagnoseFailureResponse(args = {}) {
|
|
372
492
|
let intentPlan = null;
|
|
373
493
|
const requestedProfile = args.mcpProfile || getActiveMcpProfile();
|
|
@@ -419,6 +539,28 @@ function buildContextPackResponse(args = {}) {
|
|
|
419
539
|
maxChars: Number(args.maxChars || 6000),
|
|
420
540
|
namespaces,
|
|
421
541
|
});
|
|
542
|
+
// Feed outcome-paired action receipts into the pack so an action->outcome
|
|
543
|
+
// history is available alongside lessons/rules. Additive + guarded: a
|
|
544
|
+
// receipt-store failure must never break context pack construction.
|
|
545
|
+
try {
|
|
546
|
+
const receiptEntries = buildReceiptContextEntries(args.query || '', Number(args.maxItems || 8));
|
|
547
|
+
if (Array.isArray(receiptEntries) && receiptEntries.length && Array.isArray(pack.items)) {
|
|
548
|
+
for (const entry of receiptEntries) {
|
|
549
|
+
pack.items.push({
|
|
550
|
+
id: `action-receipt_${entry && entry.score != null ? entry.score : ''}_${pack.items.length}`,
|
|
551
|
+
namespace: 'action-receipts',
|
|
552
|
+
title: 'Action receipt outcome',
|
|
553
|
+
structuredContext: { rawContent: entry && entry.text ? String(entry.text) : '' },
|
|
554
|
+
tags: ['action-receipt', 'outcome-paired'],
|
|
555
|
+
score: entry && typeof entry.score === 'number' ? entry.score : 0,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (!Array.isArray(pack.namespaces)) pack.namespaces = [];
|
|
559
|
+
if (!pack.namespaces.includes('action-receipts')) pack.namespaces.push('action-receipts');
|
|
560
|
+
}
|
|
561
|
+
} catch {
|
|
562
|
+
// ignore receipt enrichment failures
|
|
563
|
+
}
|
|
422
564
|
return toTextResult(pack);
|
|
423
565
|
}
|
|
424
566
|
|
|
@@ -532,7 +674,22 @@ function buildEstimateUncertaintyResponse(args = {}) {
|
|
|
532
674
|
|
|
533
675
|
async function callTool(name, args = {}) {
|
|
534
676
|
assertToolAllowed(name, getActiveMcpProfile());
|
|
535
|
-
|
|
677
|
+
|
|
678
|
+
// Validate tool input contract against schema
|
|
679
|
+
const { TOOLS } = require('../../scripts/tool-registry');
|
|
680
|
+
const toolDef = TOOLS.find(t => t.name === name);
|
|
681
|
+
if (toolDef && toolDef.inputSchema) {
|
|
682
|
+
const { validateToolContract } = require('../../scripts/tool-contract-validator');
|
|
683
|
+
const validation = validateToolContract(toolDef.inputSchema, args);
|
|
684
|
+
if (!validation.valid) {
|
|
685
|
+
const err = new Error(`Tool contract violation on '${name}': ${validation.errors.join('; ')}`);
|
|
686
|
+
err.errorCategory = 'contract';
|
|
687
|
+
err.isRetryable = false;
|
|
688
|
+
throw err;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (name !== 'workflow_sentinel' && process.env.THUMBGATE_DISABLE_MCP_FIREWALL !== '1') {
|
|
536
693
|
const firewallResult = (await evaluateGatesAsync(name, args)) || evaluateSecretGuard({ tool_name: name, tool_input: args });
|
|
537
694
|
if (firewallResult && firewallResult.decision === 'deny') {
|
|
538
695
|
const err = new Error(`Action blocked by Semantic Firewall: ${firewallResult.message}`);
|
|
@@ -565,9 +722,13 @@ async function callToolInner(name, args) {
|
|
|
565
722
|
if (name === 'describe_reliability_entity') name = 'describe_semantic_entity';
|
|
566
723
|
|
|
567
724
|
switch (name) {
|
|
568
|
-
case 'capture_feedback':
|
|
569
|
-
|
|
570
|
-
|
|
725
|
+
case 'capture_feedback': {
|
|
726
|
+
// Outcome-paired lessons: enrich the feedback payload with the matching
|
|
727
|
+
// action receipt (this action -> this outcome) before promotion. Returns
|
|
728
|
+
// args unchanged when there is no matching receipt (non-breaking).
|
|
729
|
+
const pairedFeedback = pairFeedbackWithReceipt(args);
|
|
730
|
+
return toCaptureFeedbackTextResult(captureFeedback(pairedFeedback));
|
|
731
|
+
}
|
|
571
732
|
case 'feedback_summary':
|
|
572
733
|
return toTextResult(feedbackSummary(Number(args.recent || 20)));
|
|
573
734
|
case 'search_lessons': {
|
|
@@ -579,6 +740,8 @@ async function callToolInner(name, args) {
|
|
|
579
740
|
tags: Array.isArray(args.tags) ? args.tags : [],
|
|
580
741
|
}));
|
|
581
742
|
}
|
|
743
|
+
case 'suggest_fix':
|
|
744
|
+
return buildSuggestFixResponse(args);
|
|
582
745
|
case 'retrieve_lessons': {
|
|
583
746
|
// Cross-encoder reranking: retrieve more candidates, then rerank for precision
|
|
584
747
|
const { retrieveWithRerankingSync } = loadOptionalModule(path.join(__dirname, '../../scripts/cross-encoder-reranker'), () => ({
|
|
@@ -597,6 +760,23 @@ async function callToolInner(name, args) {
|
|
|
597
760
|
},
|
|
598
761
|
));
|
|
599
762
|
}
|
|
763
|
+
case 'ai_component_inventory': {
|
|
764
|
+
const {
|
|
765
|
+
scanAiComponents,
|
|
766
|
+
buildCycloneDxMlBom,
|
|
767
|
+
formatInventoryText,
|
|
768
|
+
} = require('../../scripts/ai-component-inventory');
|
|
769
|
+
const rootDir = args.rootDir ? path.resolve(String(args.rootDir)) : process.cwd();
|
|
770
|
+
const inventory = scanAiComponents({
|
|
771
|
+
rootDir,
|
|
772
|
+
maxFiles: args.maxFiles ? Number(args.maxFiles) : undefined,
|
|
773
|
+
includeSnippets: args.includeSnippets !== false,
|
|
774
|
+
});
|
|
775
|
+
const format = String(args.format || 'summary').toLowerCase();
|
|
776
|
+
if (format === 'cyclonedx') return toTextResult(buildCycloneDxMlBom(inventory));
|
|
777
|
+
if (format === 'json') return toTextResult(inventory);
|
|
778
|
+
return toTextResult(formatInventoryText(inventory));
|
|
779
|
+
}
|
|
600
780
|
case 'search_thumbgate':
|
|
601
781
|
enforceLimit('search_thumbgate');
|
|
602
782
|
return toTextResult(searchThumbgate({
|
|
@@ -672,6 +852,7 @@ async function callToolInner(name, args) {
|
|
|
672
852
|
bundleId: args.bundleId,
|
|
673
853
|
partnerProfile: args.partnerProfile,
|
|
674
854
|
delegationMode: args.delegationMode,
|
|
855
|
+
enforcePlanQuality: args.enforcePlanQuality === true,
|
|
675
856
|
approved: args.approved === true,
|
|
676
857
|
repoPath: args.repoPath,
|
|
677
858
|
}));
|
|
@@ -803,6 +984,7 @@ async function callToolInner(name, args) {
|
|
|
803
984
|
summary: args.summary,
|
|
804
985
|
allowedPaths: args.allowedPaths,
|
|
805
986
|
protectedPaths: args.protectedPaths,
|
|
987
|
+
workflowContract: args.workflowContract,
|
|
806
988
|
repoPath: args.repoPath,
|
|
807
989
|
localOnly: args.localOnly === true,
|
|
808
990
|
clear: args.clear === true,
|
|
@@ -841,19 +1023,56 @@ async function callToolInner(name, args) {
|
|
|
841
1023
|
});
|
|
842
1024
|
case 'track_action': {
|
|
843
1025
|
const entry = trackAction(args.actionId, args.metadata || {});
|
|
844
|
-
|
|
1026
|
+
const result = {
|
|
845
1027
|
tracked: true,
|
|
846
1028
|
actionId: args.actionId,
|
|
847
1029
|
...entry,
|
|
1030
|
+
};
|
|
1031
|
+
// No-op / repeat signal: when the caller carries a precomputed state hash
|
|
1032
|
+
// in metadata, surface whether this exact (action, state) was already
|
|
1033
|
+
// attempted this session. Additive flag, non-breaking.
|
|
1034
|
+
const metadataStateHash = args.metadata && args.metadata.stateHash;
|
|
1035
|
+
if (metadataStateHash) {
|
|
1036
|
+
try {
|
|
1037
|
+
result.repeatSignal = isRepeatAttempt(
|
|
1038
|
+
(args.metadata && args.metadata.sessionId) || 'default',
|
|
1039
|
+
args.actionId,
|
|
1040
|
+
metadataStateHash,
|
|
1041
|
+
);
|
|
1042
|
+
} catch {
|
|
1043
|
+
// repeat detection is best-effort
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return toTextResult(result);
|
|
1047
|
+
}
|
|
1048
|
+
case 'detect_noop': {
|
|
1049
|
+
const stateHash = computeActionStateHash(args);
|
|
1050
|
+
const noop = detectNoop(args);
|
|
1051
|
+
const sessionId = args.sessionId || 'default';
|
|
1052
|
+
const repeat = isRepeatAttempt(sessionId, args.actionId, stateHash);
|
|
1053
|
+
recordActionAttempt(sessionId, args.actionId, stateHash);
|
|
1054
|
+
return toTextResult({
|
|
1055
|
+
noop: noop.noop,
|
|
1056
|
+
repeat,
|
|
1057
|
+
reason: noop.reason,
|
|
1058
|
+
stateHash,
|
|
848
1059
|
});
|
|
849
1060
|
}
|
|
1061
|
+
case 'record_action_receipt':
|
|
1062
|
+
return toTextResult(recordReceipt(args));
|
|
1063
|
+
case 'get_action_receipts':
|
|
1064
|
+
return toTextResult(
|
|
1065
|
+
args.actionId
|
|
1066
|
+
? getReceiptForAction(args.actionId)
|
|
1067
|
+
: getRecentReceipts(Number(args.limit || 20)),
|
|
1068
|
+
);
|
|
850
1069
|
case 'verify_claim':
|
|
851
|
-
return toTextResult(verifyClaimEvidence(args.claim));
|
|
1070
|
+
return toTextResult(verifyClaimEvidence(args.claim, { goalContract: args.goalContract }));
|
|
852
1071
|
case 'require_evidence_for_claim': {
|
|
853
1072
|
if (!args.claim || typeof args.claim !== 'string') {
|
|
854
1073
|
throw new Error('claim is required and must be a string');
|
|
855
1074
|
}
|
|
856
|
-
const verification = verifyClaimEvidence(args.claim);
|
|
1075
|
+
const verification = verifyClaimEvidence(args.claim, { goalContract: args.goalContract });
|
|
857
1076
|
const mode = args.mode === 'advisory' ? 'advisory' : 'blocking';
|
|
858
1077
|
const hasMatchingChecks = Array.isArray(verification.checks) && verification.checks.length > 0;
|
|
859
1078
|
const evidenceMissing = hasMatchingChecks && !verification.verified;
|
|
@@ -865,9 +1084,18 @@ async function callToolInner(name, args) {
|
|
|
865
1084
|
const { recordAuditEvent } = require('../../scripts/audit-trail');
|
|
866
1085
|
recordAuditEvent({
|
|
867
1086
|
toolName: 'require_evidence_for_claim',
|
|
868
|
-
toolInput: {
|
|
1087
|
+
toolInput: {
|
|
1088
|
+
claim: args.claim,
|
|
1089
|
+
mode,
|
|
1090
|
+
sessionId: args.sessionId || null,
|
|
1091
|
+
goalContract: verification.goalContract && verification.goalContract.matched
|
|
1092
|
+
? verification.goalContract
|
|
1093
|
+
: null,
|
|
1094
|
+
},
|
|
869
1095
|
decision: blocking ? 'deny' : 'allow',
|
|
870
|
-
gateId:
|
|
1096
|
+
gateId: verification.goalContract && verification.goalContract.matched
|
|
1097
|
+
? 'completion_goal_contract'
|
|
1098
|
+
: 'completion_claim',
|
|
871
1099
|
message: blocking
|
|
872
1100
|
? `Completion claim blocked — missing evidence: ${missingActions.join(', ') || 'unknown'}`
|
|
873
1101
|
: `Completion claim verified (${verification.verified ? 'evidence present' : 'no matching gate'})`,
|
|
@@ -882,6 +1110,7 @@ async function callToolInner(name, args) {
|
|
|
882
1110
|
matchedChecks: hasMatchingChecks,
|
|
883
1111
|
missingActions,
|
|
884
1112
|
checks: verification.checks,
|
|
1113
|
+
goalContract: verification.goalContract,
|
|
885
1114
|
sessionId: args.sessionId || null,
|
|
886
1115
|
});
|
|
887
1116
|
}
|
|
@@ -966,7 +1195,7 @@ async function callToolInner(name, args) {
|
|
|
966
1195
|
case 'register_claim_gate':
|
|
967
1196
|
return toTextResult(registerClaimGate(args.claimPattern, args.requiredActions, args.message));
|
|
968
1197
|
case 'gate_stats':
|
|
969
|
-
return toTextResult(loadGateStats());
|
|
1198
|
+
return toTextResult(mergeRepeatMetricIntoGateStats(loadGateStats()));
|
|
970
1199
|
case 'dashboard':
|
|
971
1200
|
return toTextResult(generateDashboard(getFeedbackPaths().FEEDBACK_DIR));
|
|
972
1201
|
case 'org_dashboard':
|
|
@@ -1068,6 +1297,15 @@ async function callToolInner(name, args) {
|
|
|
1068
1297
|
},
|
|
1069
1298
|
});
|
|
1070
1299
|
}
|
|
1300
|
+
case 'parallel_workflow': {
|
|
1301
|
+
const { executeWorkflow } = require('../../scripts/parallel-workflow-orchestrator');
|
|
1302
|
+
const results = await executeWorkflow(args.objective, {
|
|
1303
|
+
concurrency: args.concurrency,
|
|
1304
|
+
timeoutMs: args.timeoutMs,
|
|
1305
|
+
cwd: resolveWorkspaceCwd(args.cwd),
|
|
1306
|
+
});
|
|
1307
|
+
return toTextResult(results);
|
|
1308
|
+
}
|
|
1071
1309
|
case 'open_feedback_session':
|
|
1072
1310
|
return toTextResult(openFeedbackSession(args.feedbackEventId, args.signal, args.initialContext));
|
|
1073
1311
|
case 'append_feedback_context':
|
|
@@ -1257,6 +1495,19 @@ function startStdioServer() {
|
|
|
1257
1495
|
acquireLock();
|
|
1258
1496
|
|
|
1259
1497
|
process.stdin.resume();
|
|
1498
|
+
|
|
1499
|
+
// Self-terminate when the client disconnects (stdin EOF/close). A stdio MCP
|
|
1500
|
+
// server must not outlive its parent: clients spawn it over stdin/stdout and
|
|
1501
|
+
// a well-behaved server exits when that pipe closes. Without this, children
|
|
1502
|
+
// abandoned by a client that exits without killing them linger forever and
|
|
1503
|
+
// accumulate. WHY: on 2026-06-05, 117 orphaned `thumbgate serve` processes
|
|
1504
|
+
// (spawned by a Codex app-server over ~4 days, never reaped) piled up and
|
|
1505
|
+
// helped exhaust a 24GB host. The existing exit handlers (cleanupLock /
|
|
1506
|
+
// cleanupSessionLock registered on 'exit') run via process.exit(0).
|
|
1507
|
+
const exitOnDisconnect = () => process.exit(0);
|
|
1508
|
+
process.stdin.on('end', exitOnDisconnect);
|
|
1509
|
+
process.stdin.on('close', exitOnDisconnect);
|
|
1510
|
+
|
|
1260
1511
|
let buffer = Buffer.alloc(0);
|
|
1261
1512
|
// Auto-detect transport from first request and lock it for the session.
|
|
1262
1513
|
// mcp-proxy (Glama) sends NDJSON and expects NDJSON back.
|
|
@@ -1320,6 +1571,7 @@ module.exports = {
|
|
|
1320
1571
|
acquireLock,
|
|
1321
1572
|
toCaptureFeedbackTextResult,
|
|
1322
1573
|
formatCorrectiveActionsReminder,
|
|
1574
|
+
buildSuggestFixResponse,
|
|
1323
1575
|
__test__: {
|
|
1324
1576
|
PRIVATE_MCP_MODULES,
|
|
1325
1577
|
loadPrivateMcpModule,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ENDPOINT = 'https://api.oraclestechnologies.com/v1/guardian/analyze';
|
|
5
|
+
|
|
6
|
+
function requireApiKey(env = process.env) {
|
|
7
|
+
const key = env.ETHICORE_API_KEY || env.GUARDIAN_API_KEY || env.ORACLES_GUARDIAN_API_KEY;
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw new Error('ETHICORE_API_KEY env var is required');
|
|
10
|
+
}
|
|
11
|
+
return key;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function analyzeText(text, options = {}) {
|
|
15
|
+
if (!String(text || '').trim()) {
|
|
16
|
+
throw new Error('analyzeText requires text');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const env = options.env || process.env;
|
|
20
|
+
const endpoint = options.endpoint || env.ETHICORE_GUARDIAN_ENDPOINT || DEFAULT_ENDPOINT;
|
|
21
|
+
const apiKey = options.apiKey || requireApiKey(env);
|
|
22
|
+
const fetchImpl = options.fetch || fetch;
|
|
23
|
+
|
|
24
|
+
const response = await fetchImpl(endpoint, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ text }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const bodyText = await response.text();
|
|
34
|
+
let body = bodyText;
|
|
35
|
+
try {
|
|
36
|
+
body = bodyText ? JSON.parse(bodyText) : {};
|
|
37
|
+
} catch {
|
|
38
|
+
// Keep non-JSON body for diagnostics.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Ethicore Guardian API ${response.status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createEthicorePolicyCheck(options = {}) {
|
|
49
|
+
return async function ethicorePolicyCheck(action = {}) {
|
|
50
|
+
const toolText = [
|
|
51
|
+
action.toolName,
|
|
52
|
+
action.actionType,
|
|
53
|
+
action.command,
|
|
54
|
+
action.path,
|
|
55
|
+
action.url,
|
|
56
|
+
action.input ? JSON.stringify(action.input) : '',
|
|
57
|
+
].filter(Boolean).join('\n');
|
|
58
|
+
|
|
59
|
+
return analyzeText(toolText || JSON.stringify(action), options);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
DEFAULT_ENDPOINT,
|
|
65
|
+
analyzeText,
|
|
66
|
+
createEthicorePolicyCheck,
|
|
67
|
+
requireApiKey,
|
|
68
|
+
};
|