thumbgate 1.16.2 → 1.16.4
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/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +2 -2
- package/package.json +3 -2
- package/public/dashboard.html +298 -3
- package/public/index.html +20 -17
- package/scripts/background-agent-governance.js +229 -0
- package/scripts/dashboard.js +209 -1
- package/scripts/workflow-sentinel.js +121 -3
|
@@ -39,6 +39,9 @@ const DEFAULT_PROTECTED_FILE_GLOBS = [
|
|
|
39
39
|
];
|
|
40
40
|
const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
|
|
41
41
|
const HIGH_RISK_BASH_PATTERN = /\b(?:git\s+(?:add|commit|push)|gh\s+(?:pr\s+(?:create|merge)|workflow\s+run|release\s+create)|npm\s+publish|yarn\s+publish|pnpm\s+publish|rm\s+-rf)\b/i;
|
|
42
|
+
const BACKGROUND_AGENT_PATTERN = /\b(?:async(?:-job|-task)?|autonomous|background|cron|dispatch|heartbeat|job runner|job-runner|queue|queued|schedule|scheduled|worker|workflow run)\b/i;
|
|
43
|
+
const ECONOMIC_ACTION_PATTERN = /\b(?:billing|charge|credit memo|invoice|payment(?: link|s)?|payout|refund|stripe|subscription(?:s| creation| update| cancel| delete)?|top-?up)\b/i;
|
|
44
|
+
const CUSTOMER_SYSTEM_PATTERN = /\b(?:crm|customer|email|hubspot|intercom|mailgun|resend|salesforce|support|zendesk)\b/i;
|
|
42
45
|
|
|
43
46
|
const SURFACE_RULES = [
|
|
44
47
|
{ key: 'policy', pattern: /^(?:AGENTS\.md|CLAUDE(?:\.local)?\.md|GEMINI\.md|config\/gates\/|config\/mcp-allowlists\.json|scripts\/tool-registry\.js)/ },
|
|
@@ -234,6 +237,38 @@ function isHighRiskAction(toolName, toolInput = {}, affectedFiles = []) {
|
|
|
234
237
|
return HIGH_RISK_BASH_PATTERN.test(String(toolInput.command || ''));
|
|
235
238
|
}
|
|
236
239
|
|
|
240
|
+
function classifyActionProfile(toolInput = {}) {
|
|
241
|
+
const command = String(toolInput.command || '');
|
|
242
|
+
const metadata = toolInput && typeof toolInput.metadata === 'object' ? toolInput.metadata : {};
|
|
243
|
+
const source = String(toolInput.source || metadata.source || '');
|
|
244
|
+
const runType = String(toolInput.runType || metadata.runType || '');
|
|
245
|
+
const combined = [command, source, runType, metadata.context || ''].filter(Boolean).join(' ');
|
|
246
|
+
|
|
247
|
+
const backgroundAgent = Boolean(
|
|
248
|
+
toolInput.backgroundAgent === true
|
|
249
|
+
|| toolInput.scheduled === true
|
|
250
|
+
|| metadata.backgroundAgent === true
|
|
251
|
+
|| metadata.scheduled === true
|
|
252
|
+
|| BACKGROUND_AGENT_PATTERN.test(combined)
|
|
253
|
+
);
|
|
254
|
+
const economicAction = Boolean(
|
|
255
|
+
toolInput.economicAction === true
|
|
256
|
+
|| metadata.economicAction === true
|
|
257
|
+
|| ECONOMIC_ACTION_PATTERN.test(combined)
|
|
258
|
+
);
|
|
259
|
+
const customerSystemAction = Boolean(
|
|
260
|
+
toolInput.customerSystemAction === true
|
|
261
|
+
|| metadata.customerSystemAction === true
|
|
262
|
+
|| CUSTOMER_SYSTEM_PATTERN.test(combined)
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
backgroundAgent,
|
|
267
|
+
economicAction,
|
|
268
|
+
customerSystemAction,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
237
272
|
function isProtectedApprovalRelevant(toolName, toolInput = {}) {
|
|
238
273
|
if (EDIT_LIKE_TOOLS.has(toolName)) return true;
|
|
239
274
|
if (toolName !== 'Bash') return false;
|
|
@@ -399,6 +434,7 @@ function scoreRisk({
|
|
|
399
434
|
protectedSurface,
|
|
400
435
|
costControl,
|
|
401
436
|
workflowControl,
|
|
437
|
+
actionProfile,
|
|
402
438
|
}) {
|
|
403
439
|
const drivers = [];
|
|
404
440
|
const commandInfo = classifyCommand(toolInput.command || '');
|
|
@@ -406,6 +442,21 @@ function scoreRisk({
|
|
|
406
442
|
if (isHighRiskAction(toolName, toolInput, affectedFiles)) {
|
|
407
443
|
addDriver(drivers, 'high_risk_action', 0.18, 'Command or edit pattern is classified as high risk.');
|
|
408
444
|
}
|
|
445
|
+
if (actionProfile && actionProfile.backgroundAgent) {
|
|
446
|
+
addDriver(drivers, 'background_agent', 0.18, 'Background or scheduled agent context reduces real-time human supervision.');
|
|
447
|
+
}
|
|
448
|
+
if (actionProfile && actionProfile.economicAction) {
|
|
449
|
+
addDriver(drivers, 'economic_action', 0.24, 'Action appears to touch billing, refunds, invoices, payouts, or subscriptions.');
|
|
450
|
+
}
|
|
451
|
+
if (actionProfile && actionProfile.customerSystemAction) {
|
|
452
|
+
addDriver(drivers, 'customer_system_action', 0.14, 'Action appears to touch customer-facing systems or communications.');
|
|
453
|
+
}
|
|
454
|
+
if (actionProfile && actionProfile.backgroundAgent && actionProfile.economicAction) {
|
|
455
|
+
addDriver(drivers, 'background_economic_combo', 0.08, 'Background autonomy plus money movement needs a tighter checkpoint.');
|
|
456
|
+
}
|
|
457
|
+
if (actionProfile && actionProfile.backgroundAgent && actionProfile.customerSystemAction) {
|
|
458
|
+
addDriver(drivers, 'background_customer_combo', 0.06, 'Background autonomy plus customer systems raises downstream trust risk.');
|
|
459
|
+
}
|
|
409
460
|
if (commandInfo.isPrCreate
|
|
410
461
|
|| commandInfo.isPrMerge
|
|
411
462
|
|| commandInfo.isWorkflowRun
|
|
@@ -612,6 +663,7 @@ function buildEvidence({
|
|
|
612
663
|
normalizedAction,
|
|
613
664
|
costControl,
|
|
614
665
|
workflowControl,
|
|
666
|
+
actionProfile,
|
|
615
667
|
}) {
|
|
616
668
|
const evidence = [];
|
|
617
669
|
if (normalizedAction && normalizedAction.provider !== 'unknown') {
|
|
@@ -631,6 +683,15 @@ function buildEvidence({
|
|
|
631
683
|
evidence.push(`Workflow control ${workflowControl.mode}: ${workflowControl.reasons.join(' ')}`);
|
|
632
684
|
}
|
|
633
685
|
}
|
|
686
|
+
if (actionProfile && actionProfile.backgroundAgent) {
|
|
687
|
+
evidence.push('Background or scheduled agent context detected for this action.');
|
|
688
|
+
}
|
|
689
|
+
if (actionProfile && actionProfile.economicAction) {
|
|
690
|
+
evidence.push('Economic action keywords detected (billing, refunds, payouts, invoices, or subscriptions).');
|
|
691
|
+
}
|
|
692
|
+
if (actionProfile && actionProfile.customerSystemAction) {
|
|
693
|
+
evidence.push('Customer-system keywords detected (email, CRM, or support surface).');
|
|
694
|
+
}
|
|
634
695
|
if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
|
|
635
696
|
evidence.push(`Memory guard predicted ${memoryGuard.mode}: ${memoryGuard.reason}`);
|
|
636
697
|
}
|
|
@@ -740,6 +801,7 @@ function buildRemediations({
|
|
|
740
801
|
executionSurface,
|
|
741
802
|
costControl,
|
|
742
803
|
workflowControl,
|
|
804
|
+
actionProfile,
|
|
743
805
|
}) {
|
|
744
806
|
const remediations = [];
|
|
745
807
|
const seen = new Set();
|
|
@@ -767,6 +829,30 @@ function buildRemediations({
|
|
|
767
829
|
);
|
|
768
830
|
}
|
|
769
831
|
addIntegrityRemediations(push, integrity);
|
|
832
|
+
if (actionProfile && actionProfile.backgroundAgent) {
|
|
833
|
+
push(
|
|
834
|
+
'background_agent_checkpoint',
|
|
835
|
+
'Add an operator checkpoint',
|
|
836
|
+
'Pause the background run at a review step before it continues into code, deploy, money, or customer actions.',
|
|
837
|
+
'Queued autonomy needs an explicit approval boundary before irreversible work proceeds.'
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
if (actionProfile && actionProfile.economicAction) {
|
|
841
|
+
push(
|
|
842
|
+
'economic_action_approval',
|
|
843
|
+
'Require operator approval for money movement',
|
|
844
|
+
'Require an explicit operator checkpoint before refunds, payouts, invoice sends, or subscription changes execute.',
|
|
845
|
+
'Money-touching actions are costly to reverse and need a clear human owner.'
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
if (actionProfile && actionProfile.customerSystemAction) {
|
|
849
|
+
push(
|
|
850
|
+
'customer_system_guardrail',
|
|
851
|
+
'Add a customer-system hold point',
|
|
852
|
+
'Review customer-facing email, CRM, or support actions before sending or mutating external state.',
|
|
853
|
+
'Customer-facing actions can create trust damage even when the underlying code path is correct.'
|
|
854
|
+
);
|
|
855
|
+
}
|
|
770
856
|
if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
|
|
771
857
|
push(
|
|
772
858
|
'retrieve_lessons',
|
|
@@ -850,6 +936,16 @@ function buildReasoning(report) {
|
|
|
850
936
|
lines.push(`Deliberation policy: ${report.decisionControl.deliberation.mode} before final approval.`);
|
|
851
937
|
}
|
|
852
938
|
}
|
|
939
|
+
if (report.actionProfile) {
|
|
940
|
+
const activeFlags = [
|
|
941
|
+
report.actionProfile.backgroundAgent ? 'background agent' : null,
|
|
942
|
+
report.actionProfile.economicAction ? 'economic action' : null,
|
|
943
|
+
report.actionProfile.customerSystemAction ? 'customer system' : null,
|
|
944
|
+
].filter(Boolean);
|
|
945
|
+
if (activeFlags.length) {
|
|
946
|
+
lines.push(`Action profile: ${activeFlags.join(', ')}.`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
853
949
|
if (report.learnedPolicy && report.learnedPolicy.enabled && report.learnedPolicy.prediction) {
|
|
854
950
|
lines.push(
|
|
855
951
|
`Learned policy predicted ${report.learnedPolicy.prediction.label} (${report.learnedPolicy.prediction.confidence}).`
|
|
@@ -885,7 +981,7 @@ function getSentinelActionType(toolName) {
|
|
|
885
981
|
return '';
|
|
886
982
|
}
|
|
887
983
|
|
|
888
|
-
function classifyReversibility({ command, blastRadius, integrity, protectedSurface }) {
|
|
984
|
+
function classifyReversibility({ command, blastRadius, integrity, protectedSurface, actionProfile }) {
|
|
889
985
|
const text = String(command || '');
|
|
890
986
|
const blockers = integrity && Array.isArray(integrity.blockers) ? integrity.blockers : [];
|
|
891
987
|
const destructiveCommand = /\bgit\s+push\b.*(?:--force|-f)\b/i.test(text)
|
|
@@ -901,10 +997,16 @@ function classifyReversibility({ command, blastRadius, integrity, protectedSurfa
|
|
|
901
997
|
? protectedSurface.unapprovedProtectedFiles.length > 0
|
|
902
998
|
: false;
|
|
903
999
|
const hardBlockers = blockers.some((blocker) => /publish|merge|release|protected/i.test(String(blocker.code || '')));
|
|
1000
|
+
const economicAction = Boolean(actionProfile && actionProfile.economicAction);
|
|
1001
|
+
const customerSystemAction = Boolean(actionProfile && actionProfile.customerSystemAction);
|
|
1002
|
+
const backgroundAgent = Boolean(actionProfile && actionProfile.backgroundAgent);
|
|
904
1003
|
|
|
905
|
-
if (destructiveCommand || releaseSensitive || unapprovedProtected || hardBlockers) {
|
|
1004
|
+
if (destructiveCommand || releaseSensitive || unapprovedProtected || hardBlockers || economicAction) {
|
|
906
1005
|
return 'one_way_door';
|
|
907
1006
|
}
|
|
1007
|
+
if ((backgroundAgent && customerSystemAction) || customerSystemAction) {
|
|
1008
|
+
return 'reviewable';
|
|
1009
|
+
}
|
|
908
1010
|
if ((blastRadius && blastRadius.fileCount >= 4) || (blastRadius && blastRadius.surfaceCount >= 2)) {
|
|
909
1011
|
return 'reviewable';
|
|
910
1012
|
}
|
|
@@ -966,12 +1068,14 @@ function buildDecisionControl({
|
|
|
966
1068
|
protectedSurface,
|
|
967
1069
|
costControl,
|
|
968
1070
|
workflowControl,
|
|
1071
|
+
actionProfile,
|
|
969
1072
|
}) {
|
|
970
1073
|
const reversibility = classifyReversibility({
|
|
971
1074
|
command,
|
|
972
1075
|
blastRadius,
|
|
973
1076
|
integrity,
|
|
974
1077
|
protectedSurface,
|
|
1078
|
+
actionProfile,
|
|
975
1079
|
});
|
|
976
1080
|
const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
|
|
977
1081
|
const hasCostWarning = Boolean(costControl && costControl.mode === 'warn');
|
|
@@ -1020,7 +1124,7 @@ function buildDecisionControl({
|
|
|
1020
1124
|
};
|
|
1021
1125
|
}
|
|
1022
1126
|
|
|
1023
|
-
function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl }) {
|
|
1127
|
+
function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl, actionProfile }) {
|
|
1024
1128
|
const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
|
|
1025
1129
|
if (costControl && costControl.mode === 'block') {
|
|
1026
1130
|
return 'deny';
|
|
@@ -1067,6 +1171,9 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
|
|
|
1067
1171
|
|| blastRadius.unapprovedProtectedFiles > 0
|
|
1068
1172
|
)
|
|
1069
1173
|
);
|
|
1174
|
+
const economicAction = Boolean(actionProfile && actionProfile.economicAction);
|
|
1175
|
+
const backgroundAgent = Boolean(actionProfile && actionProfile.backgroundAgent);
|
|
1176
|
+
const customerSystemAction = Boolean(actionProfile && actionProfile.customerSystemAction);
|
|
1070
1177
|
|
|
1071
1178
|
if (lowRiskHandoff) {
|
|
1072
1179
|
return 'allow';
|
|
@@ -1074,6 +1181,9 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
|
|
|
1074
1181
|
if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
|
|
1075
1182
|
return 'deny';
|
|
1076
1183
|
}
|
|
1184
|
+
if (economicAction || (backgroundAgent && customerSystemAction) || (backgroundAgent && riskScore >= 0.3)) {
|
|
1185
|
+
return 'warn';
|
|
1186
|
+
}
|
|
1077
1187
|
if ((workflowControl && workflowControl.mode === 'warn') || (costControl && costControl.mode === 'warn') || riskScore >= 0.45 || (learnedWarning && riskScore >= 0.3) || (learnedRecall && riskScore >= 0.34)) {
|
|
1078
1188
|
return 'warn';
|
|
1079
1189
|
}
|
|
@@ -1112,6 +1222,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1112
1222
|
const affectedFiles = Array.isArray(options.affectedFiles)
|
|
1113
1223
|
? options.affectedFiles.map((filePath) => normalizePosix(filePath)).filter(Boolean)
|
|
1114
1224
|
: collectAffectedFiles(normalizedToolName, normalizedToolInput, repoRoot);
|
|
1225
|
+
const actionProfile = classifyActionProfile(normalizedToolInput);
|
|
1115
1226
|
const highRiskAction = isHighRiskAction(normalizedToolName, normalizedToolInput, affectedFiles);
|
|
1116
1227
|
const baseBranch = options.baseBranch
|
|
1117
1228
|
|| (governanceState.branchGovernance && governanceState.branchGovernance.baseBranch)
|
|
@@ -1174,6 +1285,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1174
1285
|
protectedSurface: protectedSurfaceForRisk,
|
|
1175
1286
|
costControl,
|
|
1176
1287
|
workflowControl,
|
|
1288
|
+
actionProfile,
|
|
1177
1289
|
});
|
|
1178
1290
|
const executionSurface = buildDockerSandboxPlan({
|
|
1179
1291
|
toolName: normalizedToolName,
|
|
@@ -1199,6 +1311,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1199
1311
|
command: normalizedToolInput.command || '',
|
|
1200
1312
|
costControl,
|
|
1201
1313
|
workflowControl,
|
|
1314
|
+
actionProfile,
|
|
1202
1315
|
});
|
|
1203
1316
|
const evidence = buildEvidence({
|
|
1204
1317
|
integrity,
|
|
@@ -1210,6 +1323,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1210
1323
|
normalizedAction,
|
|
1211
1324
|
costControl,
|
|
1212
1325
|
workflowControl,
|
|
1326
|
+
actionProfile,
|
|
1213
1327
|
});
|
|
1214
1328
|
const remediations = buildRemediations({
|
|
1215
1329
|
integrity,
|
|
@@ -1221,6 +1335,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1221
1335
|
executionSurface,
|
|
1222
1336
|
costControl,
|
|
1223
1337
|
workflowControl,
|
|
1338
|
+
actionProfile,
|
|
1224
1339
|
});
|
|
1225
1340
|
const summary = decision === 'allow'
|
|
1226
1341
|
? 'No predictive workflow blockers detected.'
|
|
@@ -1242,6 +1357,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1242
1357
|
evidence,
|
|
1243
1358
|
remediations,
|
|
1244
1359
|
executionSurface,
|
|
1360
|
+
actionProfile,
|
|
1245
1361
|
memoryGuard,
|
|
1246
1362
|
learnedPolicy,
|
|
1247
1363
|
taskScopeViolation,
|
|
@@ -1267,6 +1383,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1267
1383
|
protectedSurface: protectedSurfaceForRisk,
|
|
1268
1384
|
costControl,
|
|
1269
1385
|
workflowControl,
|
|
1386
|
+
actionProfile,
|
|
1270
1387
|
});
|
|
1271
1388
|
report.reasoning = buildReasoning(report);
|
|
1272
1389
|
return report;
|
|
@@ -1282,6 +1399,7 @@ module.exports = {
|
|
|
1282
1399
|
buildReasoning,
|
|
1283
1400
|
buildRemediations,
|
|
1284
1401
|
buildTaskScopeViolation,
|
|
1402
|
+
classifyActionProfile,
|
|
1285
1403
|
classifySurface,
|
|
1286
1404
|
collectAffectedFiles,
|
|
1287
1405
|
evaluateWorkflowSentinel,
|