thumbgate 1.15.0 → 1.16.1
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 +6 -6
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +5 -5
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +59 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +210 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +157 -8
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +57 -49
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +6 -6
- package/public/compare.html +29 -23
- package/public/dashboard.html +82 -10
- package/public/guide.html +28 -28
- package/public/index.html +216 -98
- package/public/learn.html +50 -22
- package/public/lessons.html +1 -1
- package/public/numbers.html +17 -17
- package/public/pro.html +82 -18
- package/scripts/agent-audit-trace.js +55 -0
- package/scripts/agent-memory-lifecycle.js +96 -0
- package/scripts/agent-readiness-plan.js +118 -0
- package/scripts/agentic-data-pipeline.js +21 -1
- package/scripts/agents-sdk-sandbox-plan.js +57 -0
- package/scripts/ai-org-governance.js +98 -0
- package/scripts/ai-search-distribution.js +43 -0
- package/scripts/artifact-agent-plan.js +81 -0
- package/scripts/billing.js +27 -8
- package/scripts/cli-schema.js +18 -2
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/dashboard-render-spec.js +1 -1
- package/scripts/dashboard.js +275 -9
- package/scripts/decision-journal.js +13 -3
- package/scripts/document-workflow-governance.js +62 -0
- package/scripts/enterprise-agent-rollout.js +34 -0
- package/scripts/experience-replay-governance.js +69 -0
- package/scripts/export-hf-dataset.js +1 -1
- package/scripts/feedback-loop.js +92 -4
- package/scripts/feedback-to-rules.js +17 -23
- package/scripts/gates-engine.js +4 -6
- package/scripts/growth-campaigns.js +49 -0
- package/scripts/harness-selector.js +16 -4
- package/scripts/hybrid-supervisor-agent.js +64 -0
- package/scripts/inference-cache-policy.js +72 -0
- package/scripts/inference-economics.js +53 -0
- package/scripts/internal-agent-bootstrap.js +12 -2
- package/scripts/knowledge-layer-plan.js +108 -0
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/llm-client.js +157 -26
- package/scripts/mailer/resend-mailer.js +112 -1
- package/scripts/mcp-transport-strategy.js +66 -0
- package/scripts/memory-store-governance.js +60 -0
- package/scripts/meta-agent-loop.js +7 -13
- package/scripts/model-access-eligibility.js +38 -0
- package/scripts/model-migration-readiness.js +55 -0
- package/scripts/operational-integrity.js +96 -3
- package/scripts/otel-declarative-config.js +56 -0
- package/scripts/perplexity-client.js +1 -1
- package/scripts/post-training-governance.js +34 -0
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/scaling-law-claims.js +60 -0
- package/scripts/security-scanner.js +1 -1
- package/scripts/self-distill-agent.js +7 -32
- package/scripts/seo-gsd.js +232 -55
- package/scripts/skill-rag-router.js +53 -0
- package/scripts/spec-gate.js +1 -1
- package/scripts/student-consistent-training.js +73 -0
- package/scripts/synthetic-data-provenance.js +98 -0
- package/scripts/task-context-result.js +81 -0
- package/scripts/telemetry-analytics.js +149 -0
- package/scripts/thompson-sampling.js +2 -2
- package/scripts/token-savings.js +7 -6
- package/scripts/token-tco.js +46 -0
- package/scripts/tool-registry.js +63 -3
- package/scripts/verification-loop.js +10 -1
- package/scripts/verifier-scoring.js +71 -0
- package/scripts/workflow-sentinel.js +284 -28
- package/scripts/workspace-agent-routines.js +118 -0
- package/src/api/server.js +381 -120
- package/scripts/analytics-report.js +0 -328
- package/scripts/autonomous-workflow.js +0 -377
- package/scripts/billing-setup.js +0 -109
- package/scripts/creator-campaigns.js +0 -239
- package/scripts/cross-encoder-reranker.js +0 -235
- package/scripts/daemon-manager.js +0 -108
- package/scripts/decision-trace.js +0 -354
- package/scripts/delegation-runtime.js +0 -896
- package/scripts/dispatch-brief.js +0 -159
- package/scripts/distribution-surfaces.js +0 -110
- package/scripts/feedback-history-distiller.js +0 -382
- package/scripts/funnel-analytics.js +0 -35
- package/scripts/history-distiller.js +0 -200
- package/scripts/hosted-job-launcher.js +0 -256
- package/scripts/intent-router.js +0 -392
- package/scripts/lesson-reranker.js +0 -263
- package/scripts/lesson-retrieval.js +0 -148
- package/scripts/managed-lesson-agent.js +0 -183
- package/scripts/operational-dashboard.js +0 -103
- package/scripts/operational-summary.js +0 -129
- package/scripts/operator-artifacts.js +0 -608
- package/scripts/optimize-context.js +0 -17
- package/scripts/org-dashboard.js +0 -206
- package/scripts/partner-orchestration.js +0 -146
- package/scripts/predictive-insights.js +0 -356
- package/scripts/pulse.js +0 -80
- package/scripts/reflector-agent.js +0 -221
- package/scripts/sales-pipeline.js +0 -681
- package/scripts/session-episode-store.js +0 -329
- package/scripts/session-health-sensor.js +0 -242
- package/scripts/session-report.js +0 -120
- package/scripts/swarm-coordinator.js +0 -81
- package/scripts/tool-kpi-tracker.js +0 -12
- package/scripts/webhook-delivery.js +0 -62
- package/scripts/workflow-sprint-intake.js +0 -475
package/src/api/server.js
CHANGED
|
@@ -3,7 +3,12 @@ const http = require('http');
|
|
|
3
3
|
const https = require('https');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { EventEmitter } = require('node:events');
|
|
6
7
|
const pkg = require('../../package.json');
|
|
8
|
+
const {
|
|
9
|
+
createUnavailableAsyncOperation,
|
|
10
|
+
loadOptionalModule,
|
|
11
|
+
} = require('../../scripts/private-core-boundary');
|
|
7
12
|
|
|
8
13
|
const POSTHOG_API_PATHS = new Set(['/capture', '/batch', '/decide', '/e', '/engage']);
|
|
9
14
|
const POSTHOG_INGEST_HOST = 'us.i.posthog.com';
|
|
@@ -50,7 +55,9 @@ const {
|
|
|
50
55
|
} = require('../../scripts/feedback-paths');
|
|
51
56
|
const {
|
|
52
57
|
readRecentConversationWindow,
|
|
53
|
-
} =
|
|
58
|
+
} = loadOptionalModule(path.join(__dirname, '../../scripts/feedback-history-distiller'), () => ({
|
|
59
|
+
readRecentConversationWindow: () => [],
|
|
60
|
+
}));
|
|
54
61
|
const {
|
|
55
62
|
readJSONL,
|
|
56
63
|
exportDpoFromMemories,
|
|
@@ -69,14 +76,6 @@ const {
|
|
|
69
76
|
const {
|
|
70
77
|
buildRubricEvaluation,
|
|
71
78
|
} = require('../../scripts/rubric-engine');
|
|
72
|
-
const {
|
|
73
|
-
listIntents,
|
|
74
|
-
planIntent,
|
|
75
|
-
} = require('../../scripts/intent-router');
|
|
76
|
-
const {
|
|
77
|
-
startHandoff,
|
|
78
|
-
completeHandoff,
|
|
79
|
-
} = require('../../scripts/delegation-runtime');
|
|
80
79
|
const {
|
|
81
80
|
bootstrapInternalAgent,
|
|
82
81
|
} = require('../../scripts/internal-agent-bootstrap');
|
|
@@ -118,14 +117,6 @@ const {
|
|
|
118
117
|
buildHostedSuccessUrl,
|
|
119
118
|
buildHostedCancelUrl,
|
|
120
119
|
} = require('../../scripts/hosted-config');
|
|
121
|
-
const {
|
|
122
|
-
PRO_MONTHLY_PRICE_DOLLARS,
|
|
123
|
-
PRO_ANNUAL_PRICE_DOLLARS,
|
|
124
|
-
TEAM_MONTHLY_PRICE_DOLLARS,
|
|
125
|
-
normalizePlanId,
|
|
126
|
-
normalizeBillingCycle,
|
|
127
|
-
normalizeSeatCount,
|
|
128
|
-
} = require('../../scripts/commercial-offer');
|
|
129
120
|
const {
|
|
130
121
|
generateSkills,
|
|
131
122
|
} = require('../../scripts/skill-generator');
|
|
@@ -146,6 +137,9 @@ const {
|
|
|
146
137
|
const {
|
|
147
138
|
evaluateWorkflowSentinel,
|
|
148
139
|
} = require('../../scripts/workflow-sentinel');
|
|
140
|
+
const {
|
|
141
|
+
normalizeProviderAction,
|
|
142
|
+
} = require('../../scripts/provider-action-normalizer');
|
|
149
143
|
const {
|
|
150
144
|
recordDecisionEvaluation,
|
|
151
145
|
recordDecisionOutcome,
|
|
@@ -163,14 +157,6 @@ const {
|
|
|
163
157
|
const {
|
|
164
158
|
getSettingsStatus,
|
|
165
159
|
} = require('../../scripts/settings-hierarchy');
|
|
166
|
-
const {
|
|
167
|
-
searchLessons,
|
|
168
|
-
} = require('../../scripts/lesson-search');
|
|
169
|
-
const {
|
|
170
|
-
updateRecordInJsonl,
|
|
171
|
-
deleteRecordFromJsonl,
|
|
172
|
-
readJSONLLocal,
|
|
173
|
-
} = require('../../scripts/lesson-synthesis');
|
|
174
160
|
const {
|
|
175
161
|
searchThumbgate,
|
|
176
162
|
} = require('../../scripts/thumbgate-search');
|
|
@@ -187,17 +173,6 @@ const {
|
|
|
187
173
|
const {
|
|
188
174
|
resolveAnalyticsWindow,
|
|
189
175
|
} = require('../../scripts/analytics-window');
|
|
190
|
-
const {
|
|
191
|
-
launchDpoExportJob,
|
|
192
|
-
launchHarnessJob,
|
|
193
|
-
pauseQueuedJob,
|
|
194
|
-
cancelQueuedJob,
|
|
195
|
-
resumeHostedJob,
|
|
196
|
-
} = require('../../scripts/hosted-job-launcher');
|
|
197
|
-
const {
|
|
198
|
-
appendWorkflowSprintLead,
|
|
199
|
-
advanceWorkflowSprintLead,
|
|
200
|
-
} = require('../../scripts/workflow-sprint-intake');
|
|
201
176
|
const {
|
|
202
177
|
importDocument,
|
|
203
178
|
listImportedDocuments,
|
|
@@ -209,6 +184,7 @@ const {
|
|
|
209
184
|
} = require('../../scripts/rate-limiter');
|
|
210
185
|
const { sendProblem, PROBLEM_TYPES } = require('../../scripts/problem-detail');
|
|
211
186
|
const { TOOLS: MCP_TOOLS } = require('../../scripts/tool-registry');
|
|
187
|
+
const resendMailer = require('../../scripts/mailer/resend-mailer');
|
|
212
188
|
const {
|
|
213
189
|
buildContextFootprintReport,
|
|
214
190
|
} = require('../../scripts/context-footprint');
|
|
@@ -253,6 +229,80 @@ const STATIC_MIME_BY_EXT = Object.freeze({
|
|
|
253
229
|
'.txt': 'text/plain; charset=utf-8',
|
|
254
230
|
'.json': 'application/json; charset=utf-8',
|
|
255
231
|
});
|
|
232
|
+
const PRIVATE_API_MODULES = Object.freeze({
|
|
233
|
+
intentRouter: path.resolve(__dirname, '../../scripts/intent-router.js'),
|
|
234
|
+
delegationRuntime: path.resolve(__dirname, '../../scripts/delegation-runtime.js'),
|
|
235
|
+
hostedJobLauncher: path.resolve(__dirname, '../../scripts/hosted-job-launcher.js'),
|
|
236
|
+
workflowSprintIntake: path.resolve(__dirname, '../../scripts/workflow-sprint-intake.js'),
|
|
237
|
+
lessonSearch: path.resolve(__dirname, '../../scripts/lesson-search.js'),
|
|
238
|
+
lessonSynthesis: path.resolve(__dirname, '../../scripts/lesson-synthesis.js'),
|
|
239
|
+
semanticLayer: path.resolve(__dirname, '../../scripts/semantic-layer.js'),
|
|
240
|
+
commercialOffer: path.resolve(__dirname, '../../scripts/commercial-offer.js'),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
function createPrivateCoreUnavailableError(feature) {
|
|
244
|
+
const error = new Error(`${feature} is only available in the ThumbGate private core or hosted runtime.`);
|
|
245
|
+
error.statusCode = 503;
|
|
246
|
+
error.code = 'PRIVATE_CORE_REQUIRED';
|
|
247
|
+
return error;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function loadPrivateApiModule(key) {
|
|
251
|
+
const modulePath = PRIVATE_API_MODULES[key];
|
|
252
|
+
if (!modulePath) {
|
|
253
|
+
throw new Error(`Unknown private API module: ${key}`);
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
return require(modulePath);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const message = String(error && error.message || '');
|
|
259
|
+
if ((error && (error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND'))
|
|
260
|
+
&& (message.includes(modulePath) || message.includes(path.basename(modulePath)))) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function requirePrivateApiModule(key, feature) {
|
|
268
|
+
const module = loadPrivateApiModule(key);
|
|
269
|
+
if (!module) {
|
|
270
|
+
throw createPrivateCoreUnavailableError(feature);
|
|
271
|
+
}
|
|
272
|
+
return module;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function getCommercialOfferModule() {
|
|
276
|
+
return requirePrivateApiModule('commercialOffer', 'Commercial offer planning');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizePlanId(value) {
|
|
280
|
+
return getCommercialOfferModule().normalizePlanId(value);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeBillingCycle(value) {
|
|
284
|
+
return getCommercialOfferModule().normalizeBillingCycle(value);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeSeatCount(value, fallback) {
|
|
288
|
+
return getCommercialOfferModule().normalizeSeatCount(value, fallback);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getLessonSynthesisModule() {
|
|
292
|
+
return requirePrivateApiModule('lessonSynthesis', 'Lesson synthesis');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function readLessonJsonl(filePath, options) {
|
|
296
|
+
return getLessonSynthesisModule().readJSONLLocal(filePath, options);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function updateLessonJsonlRecord(filePath, recordId, record) {
|
|
300
|
+
return getLessonSynthesisModule().updateRecordInJsonl(filePath, recordId, record);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function deleteLessonJsonlRecord(filePath, recordId) {
|
|
304
|
+
return getLessonSynthesisModule().deleteRecordFromJsonl(filePath, recordId);
|
|
305
|
+
}
|
|
256
306
|
|
|
257
307
|
function serveStaticFile(res, filePath, { headOnly = false, cacheSeconds = 86400 } = {}) {
|
|
258
308
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -506,11 +556,11 @@ function findRecordById(id, feedbackDir) {
|
|
|
506
556
|
const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
507
557
|
let memoryRecord = null;
|
|
508
558
|
let feedbackEvent = null;
|
|
509
|
-
const memoryRecords =
|
|
559
|
+
const memoryRecords = readLessonJsonl(memoryLogPath, { maxLines: 0 });
|
|
510
560
|
for (const rec of memoryRecords) {
|
|
511
561
|
if (rec.id === id) { memoryRecord = rec; break; }
|
|
512
562
|
}
|
|
513
|
-
const feedbackRecords =
|
|
563
|
+
const feedbackRecords = readLessonJsonl(feedbackLogPath, { maxLines: 0 });
|
|
514
564
|
for (const rec of feedbackRecords) {
|
|
515
565
|
if (rec.id === id) { feedbackEvent = rec; break; }
|
|
516
566
|
}
|
|
@@ -535,8 +585,8 @@ function updateLessonRecord(feedbackDir, lessonId, updater) {
|
|
|
535
585
|
if (!updated) return null;
|
|
536
586
|
const memoryLogPath = path.join(feedbackDir, 'memory-log.jsonl');
|
|
537
587
|
const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
538
|
-
const updatedMemory =
|
|
539
|
-
const updatedFeedback =
|
|
588
|
+
const updatedMemory = updateLessonJsonlRecord(memoryLogPath, lessonId, updated);
|
|
589
|
+
const updatedFeedback = updateLessonJsonlRecord(feedbackLogPath, lessonId, updated);
|
|
540
590
|
if (!updatedMemory && !updatedFeedback) return null;
|
|
541
591
|
return updated;
|
|
542
592
|
}
|
|
@@ -1115,6 +1165,61 @@ function resolveBillingSummaryOptions(parsed) {
|
|
|
1115
1165
|
});
|
|
1116
1166
|
}
|
|
1117
1167
|
|
|
1168
|
+
function sendInvalidAnalyticsWindowProblem(res, title, err) {
|
|
1169
|
+
sendProblem(res, {
|
|
1170
|
+
type: PROBLEM_TYPES.INVALID_REQUEST,
|
|
1171
|
+
title,
|
|
1172
|
+
status: 400,
|
|
1173
|
+
detail: err && err.message ? err.message : 'Invalid analytics window request.',
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function resolveBillingSummaryOptionsOrRespondProblem(res, parsed, invalidTitle) {
|
|
1178
|
+
try {
|
|
1179
|
+
return resolveBillingSummaryOptions(parsed);
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
sendInvalidAnalyticsWindowProblem(res, invalidTitle, err);
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
async function buildLiveDashboardData(parsed, feedbackDir) {
|
|
1187
|
+
const summaryOptions = resolveBillingSummaryOptions(parsed);
|
|
1188
|
+
const billingSummary = await getBillingSummaryLive(summaryOptions);
|
|
1189
|
+
const data = generateDashboard(feedbackDir, {
|
|
1190
|
+
analyticsWindow: summaryOptions,
|
|
1191
|
+
billingSummary,
|
|
1192
|
+
billingSource: 'live',
|
|
1193
|
+
authContext: { tier: 'pro' },
|
|
1194
|
+
});
|
|
1195
|
+
return { summaryOptions, data };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
async function loadLiveDashboardDataOrRespondProblem(res, parsed, feedbackDir, invalidTitle) {
|
|
1199
|
+
try {
|
|
1200
|
+
return await buildLiveDashboardData(parsed, feedbackDir);
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
sendInvalidAnalyticsWindowProblem(res, invalidTitle, err);
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function buildLossAnalyticsResponse(data, summaryOptions) {
|
|
1208
|
+
return {
|
|
1209
|
+
window: data.analytics.window || summaryOptions,
|
|
1210
|
+
lossAnalysis: data.analytics.lossAnalysis || null,
|
|
1211
|
+
buyerLoss: data.analytics.buyerLoss || null,
|
|
1212
|
+
funnel: data.analytics.funnel || null,
|
|
1213
|
+
revenue: data.analytics.revenue || null,
|
|
1214
|
+
telemetry: {
|
|
1215
|
+
conversionFunnel: data.analytics.telemetry && data.analytics.telemetry.conversionFunnel,
|
|
1216
|
+
behavior: data.analytics.telemetry && data.analytics.telemetry.behavior,
|
|
1217
|
+
ctas: data.analytics.telemetry && data.analytics.telemetry.ctas,
|
|
1218
|
+
visitors: data.analytics.telemetry && data.analytics.telemetry.visitors,
|
|
1219
|
+
},
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1118
1223
|
function createJourneyId(prefix) {
|
|
1119
1224
|
return createTraceId(prefix).replace(/^trace_/, `${prefix}_`);
|
|
1120
1225
|
}
|
|
@@ -1338,6 +1443,7 @@ function serveTrackedLinkRedirect({ req, res, parsed, hostedConfig, isHeadReques
|
|
|
1338
1443
|
}
|
|
1339
1444
|
|
|
1340
1445
|
function resolveCheckoutOfferSummary(metadata = {}) {
|
|
1446
|
+
const commercialOffer = getCommercialOfferModule();
|
|
1341
1447
|
const planId = normalizePlanId(metadata.planId);
|
|
1342
1448
|
const billingCycle = normalizeBillingCycle(metadata.billingCycle);
|
|
1343
1449
|
|
|
@@ -1348,8 +1454,8 @@ function resolveCheckoutOfferSummary(metadata = {}) {
|
|
|
1348
1454
|
billingCycle: 'monthly',
|
|
1349
1455
|
seatCount,
|
|
1350
1456
|
type: 'subscription',
|
|
1351
|
-
price: TEAM_MONTHLY_PRICE_DOLLARS * seatCount,
|
|
1352
|
-
priceLabel: `$${TEAM_MONTHLY_PRICE_DOLLARS}/seat/mo`,
|
|
1457
|
+
price: commercialOffer.TEAM_MONTHLY_PRICE_DOLLARS * seatCount,
|
|
1458
|
+
priceLabel: `$${commercialOffer.TEAM_MONTHLY_PRICE_DOLLARS}/seat/mo`,
|
|
1353
1459
|
};
|
|
1354
1460
|
}
|
|
1355
1461
|
|
|
@@ -1359,7 +1465,7 @@ function resolveCheckoutOfferSummary(metadata = {}) {
|
|
|
1359
1465
|
billingCycle: 'annual',
|
|
1360
1466
|
seatCount: 1,
|
|
1361
1467
|
type: 'subscription',
|
|
1362
|
-
price: PRO_ANNUAL_PRICE_DOLLARS,
|
|
1468
|
+
price: commercialOffer.PRO_ANNUAL_PRICE_DOLLARS,
|
|
1363
1469
|
priceLabel: '$149/yr',
|
|
1364
1470
|
};
|
|
1365
1471
|
}
|
|
@@ -1369,7 +1475,7 @@ function resolveCheckoutOfferSummary(metadata = {}) {
|
|
|
1369
1475
|
billingCycle: 'monthly',
|
|
1370
1476
|
seatCount: 1,
|
|
1371
1477
|
type: 'subscription',
|
|
1372
|
-
price: PRO_MONTHLY_PRICE_DOLLARS,
|
|
1478
|
+
price: commercialOffer.PRO_MONTHLY_PRICE_DOLLARS,
|
|
1373
1479
|
priceLabel: '$19/mo',
|
|
1374
1480
|
};
|
|
1375
1481
|
}
|
|
@@ -3133,6 +3239,18 @@ function createApiServer() {
|
|
|
3133
3239
|
const expectedApiKey = getExpectedApiKey();
|
|
3134
3240
|
const expectedOperatorKey = getExpectedOperatorKey();
|
|
3135
3241
|
|
|
3242
|
+
// Live-event bus. Feedback captures, prevention-rule regenerations, and
|
|
3243
|
+
// gate decisions push to this emitter; the /v1/events SSE endpoint streams
|
|
3244
|
+
// those events to connected dashboard clients so they render in real time
|
|
3245
|
+
// instead of waiting for the next manual refresh.
|
|
3246
|
+
//
|
|
3247
|
+
// See .changeset/dashboard-sse-live.md for the ROI rationale — this is a
|
|
3248
|
+
// direct application of the "persistent channel beats per-turn HTTP" pattern
|
|
3249
|
+
// to ThumbGate's dashboard surface (the primary UI for watching team
|
|
3250
|
+
// feedback flow).
|
|
3251
|
+
const eventBus = new EventEmitter();
|
|
3252
|
+
eventBus.setMaxListeners(200);
|
|
3253
|
+
|
|
3136
3254
|
return http.createServer(async (req, res) => {
|
|
3137
3255
|
const parsed = new URL(req.url, 'http://localhost');
|
|
3138
3256
|
const pathname = parsed.pathname;
|
|
@@ -3366,6 +3484,19 @@ function createApiServer() {
|
|
|
3366
3484
|
landingPath,
|
|
3367
3485
|
attribution,
|
|
3368
3486
|
}) + '\n');
|
|
3487
|
+
// Fire-and-forget welcome email. Never blocks the 200 response, never
|
|
3488
|
+
// throws — the mailer returns a structured result even on failure, so
|
|
3489
|
+
// the signup still succeeds if Resend is down or RESEND_API_KEY is unset.
|
|
3490
|
+
Promise.resolve()
|
|
3491
|
+
.then(() => resendMailer.sendNewsletterWelcomeEmail({ to: email }))
|
|
3492
|
+
.then((result) => {
|
|
3493
|
+
if (!result || result.sent !== true) {
|
|
3494
|
+
console.warn('[newsletter] welcome email not sent:', email, result && result.reason);
|
|
3495
|
+
}
|
|
3496
|
+
})
|
|
3497
|
+
.catch((err) => {
|
|
3498
|
+
console.warn('[newsletter] welcome email threw:', email, err && err.message);
|
|
3499
|
+
});
|
|
3369
3500
|
}
|
|
3370
3501
|
const journeyState = resolveJourneyState(req, parsed);
|
|
3371
3502
|
appendBestEffortTelemetry(getFeedbackPaths().FEEDBACK_DIR, {
|
|
@@ -3688,8 +3819,8 @@ async function addContext(){
|
|
|
3688
3819
|
const feedbackDir = requestSafeDataDir;
|
|
3689
3820
|
const memoryLogPath = path.join(feedbackDir, 'memory-log.jsonl');
|
|
3690
3821
|
const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
3691
|
-
const deletedMemory =
|
|
3692
|
-
const deletedFeedback =
|
|
3822
|
+
const deletedMemory = deleteLessonJsonlRecord(memoryLogPath, lessonId);
|
|
3823
|
+
const deletedFeedback = deleteLessonJsonlRecord(feedbackLogPath, lessonId);
|
|
3693
3824
|
if (!deletedMemory && !deletedFeedback) {
|
|
3694
3825
|
sendJson(res, 404, { error: 'Record not found' });
|
|
3695
3826
|
return;
|
|
@@ -4426,7 +4557,8 @@ async function addContext(){
|
|
|
4426
4557
|
const body = isFormSubmission
|
|
4427
4558
|
? await parseFormBody(req, 24 * 1024)
|
|
4428
4559
|
: await parseJsonBody(req, 24 * 1024);
|
|
4429
|
-
const
|
|
4560
|
+
const workflowSprintIntake = requirePrivateApiModule('workflowSprintIntake', 'Workflow sprint intake');
|
|
4561
|
+
const lead = workflowSprintIntake.appendWorkflowSprintLead({
|
|
4430
4562
|
...body,
|
|
4431
4563
|
traceId: body.traceId || traceId,
|
|
4432
4564
|
acquisitionId: body.acquisitionId || journeyState.acquisitionId,
|
|
@@ -4837,15 +4969,71 @@ async function addContext(){
|
|
|
4837
4969
|
return;
|
|
4838
4970
|
}
|
|
4839
4971
|
|
|
4972
|
+
// Server-Sent Events stream of live feedback / rule-regen / gate events.
|
|
4973
|
+
// Dashboard clients subscribe once (with the same Bearer auth already
|
|
4974
|
+
// required for /v1/feedback/stats) and receive pushed events as they
|
|
4975
|
+
// happen — no polling, no per-event HTTP round trip. Replaces the
|
|
4976
|
+
// implicit "refresh the page" loop that used to be the only way to see
|
|
4977
|
+
// new feedback land.
|
|
4978
|
+
if (req.method === 'GET' && pathname === '/v1/events') {
|
|
4979
|
+
res.writeHead(200, {
|
|
4980
|
+
'Content-Type': 'text/event-stream',
|
|
4981
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
4982
|
+
'Connection': 'keep-alive',
|
|
4983
|
+
'X-Accel-Buffering': 'no', // disable nginx buffering if any proxy is in front
|
|
4984
|
+
});
|
|
4985
|
+
// Initial handshake so the client knows the stream is live. Carries
|
|
4986
|
+
// the server version so clients can detect mid-session upgrades.
|
|
4987
|
+
res.write(`event: connected\ndata: ${JSON.stringify({ version: pkg.version, ts: Date.now() })}\n\n`);
|
|
4988
|
+
|
|
4989
|
+
// Both writes below can fail once the client has disconnected (the
|
|
4990
|
+
// socket is destroyed but our subscriber hasn't been removed yet).
|
|
4991
|
+
// We intentionally swallow the error and rely on the 'close'/'aborted'
|
|
4992
|
+
// handlers below to unsubscribe and clear the heartbeat — there is no
|
|
4993
|
+
// useful recovery action to take inline for a closed stream.
|
|
4994
|
+
const safeWrite = (chunk) => {
|
|
4995
|
+
try {
|
|
4996
|
+
res.write(chunk);
|
|
4997
|
+
} catch (writeErr) {
|
|
4998
|
+
// Connection closed between the emit and the flush; cleanup runs
|
|
4999
|
+
// via the 'close' listener so we don't need to act here. Keep the
|
|
5000
|
+
// exception binding so Sonar's "handle or don't catch" rule is
|
|
5001
|
+
// satisfied without adding log noise on every disconnect.
|
|
5002
|
+
void writeErr;
|
|
5003
|
+
}
|
|
5004
|
+
};
|
|
5005
|
+
|
|
5006
|
+
const onEvent = (payload) => {
|
|
5007
|
+
safeWrite(`event: ${payload.type}\ndata: ${JSON.stringify(payload)}\n\n`);
|
|
5008
|
+
};
|
|
5009
|
+
eventBus.on('broadcast', onEvent);
|
|
5010
|
+
|
|
5011
|
+
// Heartbeat every 25s keeps proxies (Railway, CDNs) from idle-closing
|
|
5012
|
+
// the connection. Clients ignore comment frames per the SSE spec.
|
|
5013
|
+
const heartbeat = setInterval(() => {
|
|
5014
|
+
safeWrite(':ping\n\n');
|
|
5015
|
+
}, 25_000);
|
|
5016
|
+
|
|
5017
|
+
const cleanup = () => {
|
|
5018
|
+
clearInterval(heartbeat);
|
|
5019
|
+
eventBus.removeListener('broadcast', onEvent);
|
|
5020
|
+
};
|
|
5021
|
+
req.on('close', cleanup);
|
|
5022
|
+
req.on('aborted', cleanup);
|
|
5023
|
+
res.on('close', cleanup);
|
|
5024
|
+
return;
|
|
5025
|
+
}
|
|
5026
|
+
|
|
4840
5027
|
if (req.method === 'GET' && pathname === '/v1/intents/catalog') {
|
|
4841
5028
|
const mcpProfile = parsed.searchParams.get('mcpProfile') || undefined;
|
|
4842
5029
|
const bundleId = parsed.searchParams.get('bundleId') || undefined;
|
|
4843
5030
|
const partnerProfile = parsed.searchParams.get('partnerProfile') || undefined;
|
|
4844
5031
|
try {
|
|
4845
|
-
const
|
|
5032
|
+
const intentRouter = requirePrivateApiModule('intentRouter', 'Intent catalog');
|
|
5033
|
+
const catalog = intentRouter.listIntents({ mcpProfile, bundleId, partnerProfile });
|
|
4846
5034
|
sendJson(res, 200, catalog);
|
|
4847
5035
|
} catch (err) {
|
|
4848
|
-
throw createHttpError(400, err.message || 'Invalid intent catalog request');
|
|
5036
|
+
throw createHttpError(err.statusCode || 400, err.message || 'Invalid intent catalog request');
|
|
4849
5037
|
}
|
|
4850
5038
|
return;
|
|
4851
5039
|
}
|
|
@@ -4853,7 +5041,8 @@ async function addContext(){
|
|
|
4853
5041
|
if (req.method === 'POST' && pathname === '/v1/intents/plan') {
|
|
4854
5042
|
const body = await parseJsonBody(req);
|
|
4855
5043
|
try {
|
|
4856
|
-
const
|
|
5044
|
+
const intentRouter = requirePrivateApiModule('intentRouter', 'Intent planning');
|
|
5045
|
+
const plan = intentRouter.planIntent({
|
|
4857
5046
|
intentId: body.intentId,
|
|
4858
5047
|
context: body.context || '',
|
|
4859
5048
|
mcpProfile: body.mcpProfile,
|
|
@@ -4865,7 +5054,7 @@ async function addContext(){
|
|
|
4865
5054
|
});
|
|
4866
5055
|
sendJson(res, 200, plan);
|
|
4867
5056
|
} catch (err) {
|
|
4868
|
-
throw createHttpError(400, err.message || 'Invalid intent plan request');
|
|
5057
|
+
throw createHttpError(err.statusCode || 400, err.message || 'Invalid intent plan request');
|
|
4869
5058
|
}
|
|
4870
5059
|
return;
|
|
4871
5060
|
}
|
|
@@ -4873,7 +5062,9 @@ async function addContext(){
|
|
|
4873
5062
|
if (req.method === 'POST' && pathname === '/v1/handoffs/start') {
|
|
4874
5063
|
const body = await parseJsonBody(req);
|
|
4875
5064
|
try {
|
|
4876
|
-
const
|
|
5065
|
+
const intentRouter = requirePrivateApiModule('intentRouter', 'Handoff planning');
|
|
5066
|
+
const delegationRuntime = requirePrivateApiModule('delegationRuntime', 'Sequential handoffs');
|
|
5067
|
+
const plan = intentRouter.planIntent({
|
|
4877
5068
|
intentId: body.intentId,
|
|
4878
5069
|
context: body.context || '',
|
|
4879
5070
|
mcpProfile: body.mcpProfile,
|
|
@@ -4883,7 +5074,7 @@ async function addContext(){
|
|
|
4883
5074
|
approved: body.approved === true,
|
|
4884
5075
|
repoPath: body.repoPath,
|
|
4885
5076
|
});
|
|
4886
|
-
const result = startHandoff({
|
|
5077
|
+
const result = delegationRuntime.startHandoff({
|
|
4887
5078
|
plan,
|
|
4888
5079
|
context: body.context || '',
|
|
4889
5080
|
mcpProfile: body.mcpProfile || plan.mcpProfile,
|
|
@@ -4902,7 +5093,8 @@ async function addContext(){
|
|
|
4902
5093
|
if (req.method === 'POST' && pathname === '/v1/handoffs/complete') {
|
|
4903
5094
|
const body = await parseJsonBody(req);
|
|
4904
5095
|
try {
|
|
4905
|
-
const
|
|
5096
|
+
const delegationRuntime = requirePrivateApiModule('delegationRuntime', 'Sequential handoffs');
|
|
5097
|
+
const result = delegationRuntime.completeHandoff({
|
|
4906
5098
|
handoffId: body.handoffId,
|
|
4907
5099
|
outcome: body.outcome,
|
|
4908
5100
|
resultContext: body.resultContext || '',
|
|
@@ -4993,7 +5185,8 @@ async function addContext(){
|
|
|
4993
5185
|
}
|
|
4994
5186
|
const inputs = parseOptionalObject(body.inputs, 'inputs') || {};
|
|
4995
5187
|
try {
|
|
4996
|
-
const
|
|
5188
|
+
const hostedJobLauncher = requirePrivateApiModule('hostedJobLauncher', 'Hosted harness jobs');
|
|
5189
|
+
const launched = hostedJobLauncher.launchHarnessJob(identifier, inputs, {
|
|
4997
5190
|
jobId: normalizeNullableText(body.jobId) || undefined,
|
|
4998
5191
|
skill: normalizeNullableText(body.skill) || undefined,
|
|
4999
5192
|
partnerProfile: normalizeNullableText(body.partnerProfile) || undefined,
|
|
@@ -5052,8 +5245,9 @@ async function addContext(){
|
|
|
5052
5245
|
throw createHttpError(409, `Job ${jobId} is already ${state.status}`);
|
|
5053
5246
|
}
|
|
5054
5247
|
|
|
5248
|
+
const hostedJobLauncher = requirePrivateApiModule('hostedJobLauncher', 'Hosted job control');
|
|
5055
5249
|
if (action === 'resume') {
|
|
5056
|
-
const launched = resumeHostedJob(jobId);
|
|
5250
|
+
const launched = hostedJobLauncher.resumeHostedJob(jobId);
|
|
5057
5251
|
sendJson(res, 202, {
|
|
5058
5252
|
accepted: true,
|
|
5059
5253
|
action,
|
|
@@ -5067,8 +5261,8 @@ async function addContext(){
|
|
|
5067
5261
|
|
|
5068
5262
|
if (IDLE_JOB_STATUSES.has(state.status)) {
|
|
5069
5263
|
const job = action === 'pause'
|
|
5070
|
-
? pauseQueuedJob(jobId, parseOptionalObject(body.metadata, 'metadata') || {})
|
|
5071
|
-
: cancelQueuedJob(jobId, parseOptionalObject(body.metadata, 'metadata') || {});
|
|
5264
|
+
? hostedJobLauncher.pauseQueuedJob(jobId, parseOptionalObject(body.metadata, 'metadata') || {})
|
|
5265
|
+
: hostedJobLauncher.cancelQueuedJob(jobId, parseOptionalObject(body.metadata, 'metadata') || {});
|
|
5072
5266
|
sendJson(res, 202, {
|
|
5073
5267
|
accepted: true,
|
|
5074
5268
|
action,
|
|
@@ -5197,7 +5391,8 @@ async function addContext(){
|
|
|
5197
5391
|
.split(',')
|
|
5198
5392
|
.map((tag) => tag.trim())
|
|
5199
5393
|
.filter(Boolean);
|
|
5200
|
-
const
|
|
5394
|
+
const lessonSearch = requirePrivateApiModule('lessonSearch', 'Lesson search');
|
|
5395
|
+
const results = lessonSearch.searchLessons(query, {
|
|
5201
5396
|
limit: Number.isFinite(limit) ? limit : 10,
|
|
5202
5397
|
category,
|
|
5203
5398
|
tags,
|
|
@@ -5311,6 +5506,19 @@ async function addContext(){
|
|
|
5311
5506
|
tags: extractTags(body.tags),
|
|
5312
5507
|
skill: body.skill,
|
|
5313
5508
|
});
|
|
5509
|
+
if (result?.accepted) {
|
|
5510
|
+
// Fan out to any connected dashboard clients so they re-render
|
|
5511
|
+
// without polling. Non-sensitive summary only (no chat history,
|
|
5512
|
+
// no evidence blobs).
|
|
5513
|
+
eventBus.emit('broadcast', {
|
|
5514
|
+
type: 'feedback',
|
|
5515
|
+
signal: body.signal,
|
|
5516
|
+
tags: Array.isArray(body.tags) ? body.tags.slice(0, 8) : [],
|
|
5517
|
+
feedbackId: result.feedbackId,
|
|
5518
|
+
promoted: Boolean(result.promoted),
|
|
5519
|
+
ts: Date.now(),
|
|
5520
|
+
});
|
|
5521
|
+
}
|
|
5314
5522
|
const code = result.accepted ? 200 : 422;
|
|
5315
5523
|
sendJson(res, code, result);
|
|
5316
5524
|
return;
|
|
@@ -5323,6 +5531,13 @@ async function addContext(){
|
|
|
5323
5531
|
? resolveSafePath(body.outputPath, { safeDataDir: requestSafeDataDir })
|
|
5324
5532
|
: undefined;
|
|
5325
5533
|
const result = writePreventionRules(outputPath, Number.isFinite(minOccurrences) ? minOccurrences : 2);
|
|
5534
|
+
// Tell live dashboard clients the rules file just changed so they can
|
|
5535
|
+
// re-fetch the summary without waiting on a poll tick.
|
|
5536
|
+
eventBus.emit('broadcast', {
|
|
5537
|
+
type: 'rules-updated',
|
|
5538
|
+
path: result.path,
|
|
5539
|
+
ts: Date.now(),
|
|
5540
|
+
});
|
|
5326
5541
|
sendJson(res, 200, {
|
|
5327
5542
|
path: result.path,
|
|
5328
5543
|
markdown: result.markdown,
|
|
@@ -5380,7 +5595,8 @@ async function addContext(){
|
|
|
5380
5595
|
|
|
5381
5596
|
if (wantsAsync) {
|
|
5382
5597
|
try {
|
|
5383
|
-
const
|
|
5598
|
+
const hostedJobLauncher = requirePrivateApiModule('hostedJobLauncher', 'Hosted DPO export jobs');
|
|
5599
|
+
const launched = hostedJobLauncher.launchDpoExportJob(paths, {
|
|
5384
5600
|
jobId: normalizeNullableText(body.jobId) || undefined,
|
|
5385
5601
|
});
|
|
5386
5602
|
sendJson(res, 202, {
|
|
@@ -5423,8 +5639,8 @@ async function addContext(){
|
|
|
5423
5639
|
const feedbackDir = requestSafeDataDir;
|
|
5424
5640
|
const memoryLogPath = path.join(feedbackDir, 'memory-log.jsonl');
|
|
5425
5641
|
const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
5426
|
-
const memories =
|
|
5427
|
-
const feedbacks =
|
|
5642
|
+
const memories = readLessonJsonl(memoryLogPath, { maxLines: 0 });
|
|
5643
|
+
const feedbacks = readLessonJsonl(feedbackLogPath, { maxLines: 0 });
|
|
5428
5644
|
|
|
5429
5645
|
// Merge into unified lesson records
|
|
5430
5646
|
const lessonMap = new Map();
|
|
@@ -5514,7 +5730,7 @@ async function addContext(){
|
|
|
5514
5730
|
const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
5515
5731
|
|
|
5516
5732
|
// Load existing IDs for dedup
|
|
5517
|
-
const existing =
|
|
5733
|
+
const existing = readLessonJsonl(feedbackLogPath, { maxLines: 0 });
|
|
5518
5734
|
const existingIds = new Set(existing.map((r) => r.id).filter(Boolean));
|
|
5519
5735
|
// Also dedup by title+signal content hash
|
|
5520
5736
|
const existingHashes = new Set(existing.map((r) => {
|
|
@@ -5699,12 +5915,12 @@ async function addContext(){
|
|
|
5699
5915
|
|
|
5700
5916
|
// GET /v1/semantic/describe — get canonical definition of a business entity
|
|
5701
5917
|
if (req.method === 'GET' && pathname === '/v1/semantic/describe') {
|
|
5702
|
-
const
|
|
5918
|
+
const semanticLayer = requirePrivateApiModule('semanticLayer', 'Semantic schema');
|
|
5703
5919
|
const type = parsed.query.type;
|
|
5704
5920
|
if (!type) {
|
|
5705
5921
|
throw createHttpError(400, 'type query parameter is required');
|
|
5706
5922
|
}
|
|
5707
|
-
const schema = describeSemanticSchema();
|
|
5923
|
+
const schema = semanticLayer.describeSemanticSchema();
|
|
5708
5924
|
const entity = schema.entities[type] || schema.metrics[type];
|
|
5709
5925
|
if (!entity) {
|
|
5710
5926
|
sendProblem(res, {
|
|
@@ -5780,16 +5996,12 @@ async function addContext(){
|
|
|
5780
5996
|
return;
|
|
5781
5997
|
}
|
|
5782
5998
|
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
title: 'Invalid billing summary query',
|
|
5790
|
-
status: 400,
|
|
5791
|
-
detail: err && err.message ? err.message : 'Invalid analytics window request.',
|
|
5792
|
-
});
|
|
5999
|
+
const summaryOptions = resolveBillingSummaryOptionsOrRespondProblem(
|
|
6000
|
+
res,
|
|
6001
|
+
parsed,
|
|
6002
|
+
'Invalid billing summary query',
|
|
6003
|
+
);
|
|
6004
|
+
if (!summaryOptions) {
|
|
5793
6005
|
return;
|
|
5794
6006
|
}
|
|
5795
6007
|
|
|
@@ -5813,7 +6025,8 @@ async function addContext(){
|
|
|
5813
6025
|
const { FEEDBACK_DIR } = getFeedbackPaths();
|
|
5814
6026
|
try {
|
|
5815
6027
|
const body = await parseJsonBody(req, 24 * 1024);
|
|
5816
|
-
const
|
|
6028
|
+
const workflowSprintIntake = requirePrivateApiModule('workflowSprintIntake', 'Workflow sprint intake');
|
|
6029
|
+
const result = workflowSprintIntake.advanceWorkflowSprintLead(body, { feedbackDir: FEEDBACK_DIR });
|
|
5817
6030
|
|
|
5818
6031
|
appendBestEffortTelemetry(FEEDBACK_DIR, {
|
|
5819
6032
|
eventType: 'workflow_sprint_lead_advanced',
|
|
@@ -5910,28 +6123,36 @@ async function addContext(){
|
|
|
5910
6123
|
return;
|
|
5911
6124
|
}
|
|
5912
6125
|
|
|
6126
|
+
// GET /v1/analytics/losses -- explain where buyer dollars are falling out of the funnel
|
|
6127
|
+
if (req.method === 'GET' && pathname === '/v1/analytics/losses') {
|
|
6128
|
+
const dashboardResult = await loadLiveDashboardDataOrRespondProblem(
|
|
6129
|
+
res,
|
|
6130
|
+
parsed,
|
|
6131
|
+
requestFeedbackDir,
|
|
6132
|
+
'Invalid loss analytics query',
|
|
6133
|
+
);
|
|
6134
|
+
if (!dashboardResult) {
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
const { summaryOptions, data } = dashboardResult;
|
|
6138
|
+
|
|
6139
|
+
sendJson(res, 200, buildLossAnalyticsResponse(data, summaryOptions));
|
|
6140
|
+
return;
|
|
6141
|
+
}
|
|
6142
|
+
|
|
5913
6143
|
// GET /v1/dashboard -- Full ThumbGate dashboard JSON
|
|
5914
6144
|
if (req.method === 'GET' && pathname === '/v1/dashboard') {
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
status: 400,
|
|
5923
|
-
detail: err && err.message ? err.message : 'Invalid analytics window request.',
|
|
5924
|
-
});
|
|
6145
|
+
const dashboardResult = await loadLiveDashboardDataOrRespondProblem(
|
|
6146
|
+
res,
|
|
6147
|
+
parsed,
|
|
6148
|
+
requestFeedbackDir,
|
|
6149
|
+
'Invalid dashboard query',
|
|
6150
|
+
);
|
|
6151
|
+
if (!dashboardResult) {
|
|
5925
6152
|
return;
|
|
5926
6153
|
}
|
|
6154
|
+
const { data } = dashboardResult;
|
|
5927
6155
|
|
|
5928
|
-
const billingSummary = await getBillingSummaryLive(summaryOptions);
|
|
5929
|
-
const data = generateDashboard(requestFeedbackDir, {
|
|
5930
|
-
analyticsWindow: summaryOptions,
|
|
5931
|
-
billingSummary,
|
|
5932
|
-
billingSource: 'live',
|
|
5933
|
-
authContext: { tier: 'pro' },
|
|
5934
|
-
});
|
|
5935
6156
|
sendJson(res, 200, data);
|
|
5936
6157
|
return;
|
|
5937
6158
|
}
|
|
@@ -5968,27 +6189,18 @@ async function addContext(){
|
|
|
5968
6189
|
|
|
5969
6190
|
// GET /v1/dashboard/render-spec -- Constrained hosted dashboard JSON spec
|
|
5970
6191
|
if (req.method === 'GET' && pathname === '/v1/dashboard/render-spec') {
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
status: 400,
|
|
5979
|
-
detail: err && err.message ? err.message : 'Invalid analytics window request.',
|
|
5980
|
-
});
|
|
6192
|
+
const dashboardResult = await loadLiveDashboardDataOrRespondProblem(
|
|
6193
|
+
res,
|
|
6194
|
+
parsed,
|
|
6195
|
+
requestFeedbackDir,
|
|
6196
|
+
'Invalid render-spec query',
|
|
6197
|
+
);
|
|
6198
|
+
if (!dashboardResult) {
|
|
5981
6199
|
return;
|
|
5982
6200
|
}
|
|
6201
|
+
const { summaryOptions, data } = dashboardResult;
|
|
5983
6202
|
|
|
5984
6203
|
try {
|
|
5985
|
-
const billingSummary = await getBillingSummaryLive(summaryOptions);
|
|
5986
|
-
const data = generateDashboard(requestFeedbackDir, {
|
|
5987
|
-
analyticsWindow: summaryOptions,
|
|
5988
|
-
billingSummary,
|
|
5989
|
-
billingSource: 'live',
|
|
5990
|
-
authContext: { tier: 'pro' },
|
|
5991
|
-
});
|
|
5992
6204
|
const renderSpec = buildDashboardRenderSpec(data, {
|
|
5993
6205
|
view: parsed.searchParams.get('view') || undefined,
|
|
5994
6206
|
now: summaryOptions.now,
|
|
@@ -6007,42 +6219,87 @@ async function addContext(){
|
|
|
6007
6219
|
|
|
6008
6220
|
if (req.method === 'POST' && pathname === '/v1/decisions/evaluate') {
|
|
6009
6221
|
const body = await parseJsonBody(req);
|
|
6010
|
-
|
|
6222
|
+
const normalizedRequestAction = normalizeProviderAction(body);
|
|
6223
|
+
const hasProviderNativeAction = Boolean(
|
|
6224
|
+
body.providerToolCall
|
|
6225
|
+
|| body.toolCall
|
|
6226
|
+
|| body.toolUse
|
|
6227
|
+
|| body.content
|
|
6228
|
+
|| body.mcp
|
|
6229
|
+
|| body.mcpToolCall
|
|
6230
|
+
|| body.method === 'tools/call'
|
|
6231
|
+
);
|
|
6232
|
+
if (!body.toolName && !hasProviderNativeAction) {
|
|
6011
6233
|
sendProblem(res, {
|
|
6012
6234
|
type: PROBLEM_TYPES.BAD_REQUEST,
|
|
6013
6235
|
title: 'Bad Request',
|
|
6014
6236
|
status: 400,
|
|
6015
|
-
detail: 'toolName is required.',
|
|
6237
|
+
detail: 'toolName or provider tool call is required.',
|
|
6016
6238
|
});
|
|
6017
6239
|
return;
|
|
6018
6240
|
}
|
|
6019
6241
|
|
|
6020
|
-
const
|
|
6242
|
+
const changedFiles = Array.isArray(body.changedFiles)
|
|
6243
|
+
? body.changedFiles
|
|
6244
|
+
: normalizedRequestAction.affectedFiles;
|
|
6245
|
+
const scopeState = getScopeState();
|
|
6246
|
+
const toolInput = {
|
|
6021
6247
|
command: body.command,
|
|
6022
6248
|
path: body.filePath,
|
|
6023
|
-
changed_files:
|
|
6249
|
+
changed_files: changedFiles,
|
|
6024
6250
|
repoPath: body.repoPath,
|
|
6025
6251
|
baseBranch: body.baseBranch,
|
|
6026
|
-
|
|
6252
|
+
providerToolCall: body.providerToolCall,
|
|
6253
|
+
toolCall: body.toolCall,
|
|
6254
|
+
toolUse: body.toolUse,
|
|
6255
|
+
content: body.content,
|
|
6256
|
+
input: body.input,
|
|
6257
|
+
arguments: body.arguments,
|
|
6258
|
+
method: body.method,
|
|
6259
|
+
params: body.params,
|
|
6260
|
+
mcp: body.mcp,
|
|
6261
|
+
mcpToolCall: body.mcpToolCall,
|
|
6262
|
+
budget: body.budget,
|
|
6263
|
+
usage: body.usage,
|
|
6264
|
+
};
|
|
6265
|
+
|
|
6266
|
+
const report = evaluateWorkflowSentinel(normalizedRequestAction.toolName || body.toolName, toolInput, {
|
|
6267
|
+
provider: body.provider,
|
|
6268
|
+
model: body.model,
|
|
6269
|
+
normalizedAction: normalizedRequestAction,
|
|
6270
|
+
usage: body.usage,
|
|
6271
|
+
tokenEstimate: body.tokenEstimate,
|
|
6272
|
+
costUsd: body.costUsd,
|
|
6273
|
+
budget: body.budget,
|
|
6027
6274
|
repoPath: body.repoPath,
|
|
6028
6275
|
baseBranch: body.baseBranch,
|
|
6029
|
-
affectedFiles:
|
|
6276
|
+
affectedFiles: changedFiles.length > 0 ? changedFiles : undefined,
|
|
6030
6277
|
requirePrForReleaseSensitive: body.requirePrForReleaseSensitive === true,
|
|
6031
6278
|
requireVersionNotBehindBase: body.requireVersionNotBehindBase === true,
|
|
6032
|
-
governanceState:
|
|
6279
|
+
governanceState: {
|
|
6280
|
+
...scopeState,
|
|
6281
|
+
branchGovernance: body.workflowDispatch && typeof body.workflowDispatch === 'object'
|
|
6282
|
+
? {
|
|
6283
|
+
...(scopeState.branchGovernance || {}),
|
|
6284
|
+
workflowDispatch: body.workflowDispatch,
|
|
6285
|
+
}
|
|
6286
|
+
: scopeState.branchGovernance,
|
|
6287
|
+
},
|
|
6033
6288
|
feedbackDir: requestFeedbackDir,
|
|
6034
6289
|
});
|
|
6035
6290
|
const evaluation = recordDecisionEvaluation(report, {
|
|
6036
6291
|
source: 'api',
|
|
6037
|
-
toolName:
|
|
6292
|
+
toolName: report.toolName,
|
|
6038
6293
|
toolInput: {
|
|
6039
6294
|
command: body.command,
|
|
6040
6295
|
filePath: body.filePath,
|
|
6041
|
-
changedFiles
|
|
6296
|
+
changedFiles,
|
|
6042
6297
|
repoPath: body.repoPath,
|
|
6043
6298
|
baseBranch: body.baseBranch,
|
|
6299
|
+
normalizedAction: report.normalizedAction,
|
|
6300
|
+
costControl: report.costControl,
|
|
6044
6301
|
},
|
|
6045
|
-
changedFiles
|
|
6302
|
+
changedFiles,
|
|
6046
6303
|
}, {
|
|
6047
6304
|
feedbackDir: requestFeedbackDir,
|
|
6048
6305
|
});
|
|
@@ -6252,9 +6509,13 @@ module.exports = {
|
|
|
6252
6509
|
startServer,
|
|
6253
6510
|
__test__: {
|
|
6254
6511
|
buildCheckoutFallbackUrl,
|
|
6512
|
+
createPrivateCoreUnavailableError,
|
|
6255
6513
|
buildPosthogProxyRequestOptions,
|
|
6256
6514
|
getPosthogProxyPath,
|
|
6257
6515
|
isAllowedPosthogProxyPath,
|
|
6516
|
+
PRIVATE_API_MODULES,
|
|
6517
|
+
loadPrivateApiModule,
|
|
6518
|
+
requirePrivateApiModule,
|
|
6258
6519
|
renderSitemapXml,
|
|
6259
6520
|
renderPackagedDashboardHtml,
|
|
6260
6521
|
renderPackagedLessonsHtml,
|