thumbgate 0.9.14 → 1.1.0
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +1 -0
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +105 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/forge/forge.yaml +28 -0
- package/adapters/mcp/server-stdio.js +41 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +18 -3
- package/config/mcp-allowlists.json +11 -0
- package/openapi/openapi.yaml +105 -0
- package/package.json +7 -5
- package/plugins/amp-skill/INSTALL.md +3 -4
- package/plugins/amp-skill/SKILL.md +0 -1
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-skill/INSTALL.md +1 -2
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +1 -0
- package/public/dashboard.html +1 -1
- package/public/guide.html +1 -1
- package/public/index.html +8 -4
- package/public/learn/agent-harness-pattern.html +1 -1
- package/public/learn/ai-agent-persistent-memory.html +1 -1
- package/public/learn/mcp-pre-action-gates-explained.html +1 -1
- package/public/learn/stop-ai-agent-force-push.html +1 -1
- package/public/learn/vibe-coding-safety-net.html +1 -1
- package/public/learn.html +1 -1
- package/public/lessons.html +1 -1
- package/public/pro.html +1 -1
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/async-job-runner.js +84 -24
- package/scripts/auto-wire-hooks.js +59 -1
- package/scripts/context-manager.js +330 -0
- package/scripts/dashboard.js +1 -1
- package/scripts/distribution-surfaces.js +12 -0
- package/scripts/ensure-repo-bootstrap.js +15 -14
- package/scripts/export-hf-dataset.js +293 -0
- package/scripts/gates-engine.js +96 -10
- package/scripts/hook-auto-capture.sh +1 -1
- package/scripts/hosted-job-launcher.js +260 -0
- package/scripts/managed-dpo-export.js +91 -0
- package/scripts/obsidian-export.js +0 -1
- package/scripts/operational-integrity.js +50 -7
- package/scripts/prove-lancedb.js +62 -4
- package/scripts/publish-decision.js +16 -0
- package/scripts/self-healing-check.js +6 -1
- package/scripts/social-analytics/load-env.js +33 -2
- package/scripts/social-analytics/store.js +200 -2
- package/scripts/sync-version.js +18 -11
- package/scripts/tool-registry.js +48 -0
- package/scripts/train_from_feedback.py +0 -4
- package/scripts/workflow-sentinel.js +793 -0
- package/src/api/server.js +205 -27
- /package/scripts/{rlhf_session_start.sh → thumbgate_session_start.sh} +0 -0
package/src/api/server.js
CHANGED
|
@@ -52,6 +52,11 @@ const {
|
|
|
52
52
|
const {
|
|
53
53
|
buildCloudflareSandboxPlan,
|
|
54
54
|
} = require('../../scripts/cloudflare-dynamic-sandbox');
|
|
55
|
+
const {
|
|
56
|
+
listJobStates,
|
|
57
|
+
readJobState,
|
|
58
|
+
requestJobControl,
|
|
59
|
+
} = require('../../scripts/async-job-runner');
|
|
55
60
|
const {
|
|
56
61
|
loadModel,
|
|
57
62
|
getReliability,
|
|
@@ -136,6 +141,13 @@ const {
|
|
|
136
141
|
const {
|
|
137
142
|
resolveAnalyticsWindow,
|
|
138
143
|
} = require('../../scripts/analytics-window');
|
|
144
|
+
const {
|
|
145
|
+
launchDpoExportJob,
|
|
146
|
+
launchHarnessJob,
|
|
147
|
+
pauseQueuedJob,
|
|
148
|
+
cancelQueuedJob,
|
|
149
|
+
resumeHostedJob,
|
|
150
|
+
} = require('../../scripts/hosted-job-launcher');
|
|
139
151
|
const {
|
|
140
152
|
appendWorkflowSprintLead,
|
|
141
153
|
advanceWorkflowSprintLead,
|
|
@@ -165,6 +177,9 @@ const SESSION_COOKIE_NAME = 'thumbgate_session_id';
|
|
|
165
177
|
const ACQUISITION_COOKIE_NAME = 'thumbgate_acquisition_id';
|
|
166
178
|
const VISITOR_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 90;
|
|
167
179
|
const BUILD_METADATA = resolveBuildMetadata();
|
|
180
|
+
const TERMINAL_JOB_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
181
|
+
const IDLE_JOB_STATUSES = new Set(['queued', 'paused', 'resume_requested']);
|
|
182
|
+
const JOB_CONTROL_ACTIONS = new Set(['pause', 'cancel', 'resume']);
|
|
168
183
|
|
|
169
184
|
// ---------------------------------------------------------------------------
|
|
170
185
|
// Stripe event tracking helpers
|
|
@@ -1215,6 +1230,7 @@ nav .container { display: flex; justify-content: space-between; align-items: cen
|
|
|
1215
1230
|
.actions-bar { flex-direction: column; }
|
|
1216
1231
|
}
|
|
1217
1232
|
</style>
|
|
1233
|
+
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
|
|
1218
1234
|
</head>
|
|
1219
1235
|
<body>
|
|
1220
1236
|
<nav><div class="container">
|
|
@@ -1545,6 +1561,7 @@ function renderCheckoutSuccessPage(runtimeConfig) {
|
|
|
1545
1561
|
font-size: 14px;
|
|
1546
1562
|
}
|
|
1547
1563
|
</style>
|
|
1564
|
+
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
|
|
1548
1565
|
</head>
|
|
1549
1566
|
<body>
|
|
1550
1567
|
<main>
|
|
@@ -1824,6 +1841,7 @@ function renderCheckoutCancelledPage(runtimeConfig) {
|
|
|
1824
1841
|
margin-top: 12px;
|
|
1825
1842
|
}
|
|
1826
1843
|
</style>
|
|
1844
|
+
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
|
|
1827
1845
|
</head>
|
|
1828
1846
|
<body>
|
|
1829
1847
|
<main>
|
|
@@ -1996,6 +2014,7 @@ function renderWorkflowSprintIntakeResultPage(runtimeConfig, { title, detail, le
|
|
|
1996
2014
|
border: 1px solid var(--line);
|
|
1997
2015
|
}
|
|
1998
2016
|
</style>
|
|
2017
|
+
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
|
|
1999
2018
|
</head>
|
|
2000
2019
|
<body>
|
|
2001
2020
|
<main>
|
|
@@ -2170,6 +2189,56 @@ function resolveSafePath(inputPath, { mustExist = false, safeDataDir } = {}) {
|
|
|
2170
2189
|
return resolved;
|
|
2171
2190
|
}
|
|
2172
2191
|
|
|
2192
|
+
function resolveDpoExportPaths(body = {}, options = {}) {
|
|
2193
|
+
const { safeDataDir, fallbackMemoryLogPath = null } = options;
|
|
2194
|
+
return {
|
|
2195
|
+
inputPath: body.inputPath
|
|
2196
|
+
? resolveSafePath(body.inputPath, { mustExist: true, safeDataDir })
|
|
2197
|
+
: null,
|
|
2198
|
+
memoryLogPath: body.memoryLogPath
|
|
2199
|
+
? resolveSafePath(body.memoryLogPath, { mustExist: true, safeDataDir })
|
|
2200
|
+
: null,
|
|
2201
|
+
outputPath: body.outputPath
|
|
2202
|
+
? resolveSafePath(body.outputPath, { safeDataDir })
|
|
2203
|
+
: null,
|
|
2204
|
+
fallbackMemoryLogPath,
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function loadDpoExportMemories({ inputPath, memoryLogPath, fallbackMemoryLogPath = null }) {
|
|
2209
|
+
if (inputPath) {
|
|
2210
|
+
const raw = fs.readFileSync(inputPath, 'utf-8');
|
|
2211
|
+
const parsedMemories = JSON.parse(raw);
|
|
2212
|
+
return Array.isArray(parsedMemories) ? parsedMemories : parsedMemories.memories || [];
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
return readJSONL(memoryLogPath || fallbackMemoryLogPath || DEFAULT_LOCAL_MEMORY_LOG);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function parseJobStatuses(value) {
|
|
2219
|
+
if (!value) return [];
|
|
2220
|
+
return String(value)
|
|
2221
|
+
.split(',')
|
|
2222
|
+
.map((entry) => entry.trim())
|
|
2223
|
+
.filter(Boolean);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function readHostedJobOrThrow(jobId) {
|
|
2227
|
+
const state = readJobState(jobId);
|
|
2228
|
+
if (!state) {
|
|
2229
|
+
throw createHttpError(404, `Job not found: ${jobId}`);
|
|
2230
|
+
}
|
|
2231
|
+
return state;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function normalizeJobIdFromPath(pathname, suffix = '') {
|
|
2235
|
+
const pattern = suffix
|
|
2236
|
+
? new RegExp(`^/v1/jobs/([^/]+)${suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`)
|
|
2237
|
+
: /^\/v1\/jobs\/([^/]+)$/;
|
|
2238
|
+
const match = pathname.match(pattern);
|
|
2239
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2173
2242
|
function createApiServer() {
|
|
2174
2243
|
const expectedApiKey = getExpectedApiKey();
|
|
2175
2244
|
|
|
@@ -2779,7 +2848,7 @@ async function addContext(){
|
|
|
2779
2848
|
version: pkg.version,
|
|
2780
2849
|
status: 'ok',
|
|
2781
2850
|
docs: 'https://github.com/IgorGanapolsky/ThumbGate',
|
|
2782
|
-
endpoints: ['/health', '/dashboard', '/guide', '/learn', '/pro', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/feedback/summary', '/v1/lessons/search', '/v1/search', '/v1/dashboard', '/v1/dashboard/render-spec', '/v1/settings/status', '/v1/dpo/export', '/v1/analytics/databricks/export'],
|
|
2851
|
+
endpoints: ['/health', '/dashboard', '/guide', '/learn', '/pro', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/feedback/summary', '/v1/lessons/search', '/v1/search', '/v1/dashboard', '/v1/dashboard/render-spec', '/v1/settings/status', '/v1/dpo/export', '/v1/jobs', '/v1/jobs/harness', '/v1/analytics/databricks/export'],
|
|
2783
2852
|
}, {}, {
|
|
2784
2853
|
headOnly: isHeadRequest,
|
|
2785
2854
|
});
|
|
@@ -3715,6 +3784,112 @@ async function addContext(){
|
|
|
3715
3784
|
return;
|
|
3716
3785
|
}
|
|
3717
3786
|
|
|
3787
|
+
if (req.method === 'POST' && pathname === '/v1/jobs/harness') {
|
|
3788
|
+
const body = await parseJsonBody(req);
|
|
3789
|
+
const identifier = body.harness || body.harnessId;
|
|
3790
|
+
if (!identifier) {
|
|
3791
|
+
throw createHttpError(400, 'harness is required');
|
|
3792
|
+
}
|
|
3793
|
+
const inputs = parseOptionalObject(body.inputs, 'inputs') || {};
|
|
3794
|
+
try {
|
|
3795
|
+
const launched = launchHarnessJob(identifier, inputs, {
|
|
3796
|
+
jobId: normalizeNullableText(body.jobId) || undefined,
|
|
3797
|
+
skill: normalizeNullableText(body.skill) || undefined,
|
|
3798
|
+
partnerProfile: normalizeNullableText(body.partnerProfile) || undefined,
|
|
3799
|
+
autoImprove: body.autoImprove !== false,
|
|
3800
|
+
});
|
|
3801
|
+
sendJson(res, 202, {
|
|
3802
|
+
accepted: true,
|
|
3803
|
+
jobId: launched.jobId,
|
|
3804
|
+
status: launched.state.status,
|
|
3805
|
+
launchMode: launched.launchMode,
|
|
3806
|
+
pid: launched.pid,
|
|
3807
|
+
statusUrl: `/v1/jobs/${encodeURIComponent(launched.jobId)}`,
|
|
3808
|
+
job: launched.state,
|
|
3809
|
+
});
|
|
3810
|
+
} catch (err) {
|
|
3811
|
+
throw createHttpError(err.statusCode || 400, err.message || 'Invalid hosted harness request');
|
|
3812
|
+
}
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
if (req.method === 'GET' && pathname === '/v1/jobs') {
|
|
3817
|
+
const limit = Number(parsed.searchParams.get('limit') || 20);
|
|
3818
|
+
const statuses = parseJobStatuses(parsed.searchParams.get('status'));
|
|
3819
|
+
const jobs = listJobStates({
|
|
3820
|
+
limit: Number.isFinite(limit) ? limit : 20,
|
|
3821
|
+
statuses,
|
|
3822
|
+
});
|
|
3823
|
+
sendJson(res, 200, {
|
|
3824
|
+
total: jobs.length,
|
|
3825
|
+
jobs,
|
|
3826
|
+
});
|
|
3827
|
+
return;
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
{
|
|
3831
|
+
const jobId = normalizeJobIdFromPath(pathname);
|
|
3832
|
+
if (req.method === 'GET' && jobId) {
|
|
3833
|
+
sendJson(res, 200, {
|
|
3834
|
+
job: readHostedJobOrThrow(jobId),
|
|
3835
|
+
});
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
{
|
|
3841
|
+
const jobId = normalizeJobIdFromPath(pathname, '/control');
|
|
3842
|
+
if (req.method === 'POST' && jobId) {
|
|
3843
|
+
const state = readHostedJobOrThrow(jobId);
|
|
3844
|
+
const body = await parseJsonBody(req);
|
|
3845
|
+
const action = normalizeNullableText(body.action);
|
|
3846
|
+
if (!action || !JOB_CONTROL_ACTIONS.has(action)) {
|
|
3847
|
+
throw createHttpError(400, 'action must be one of pause, cancel, or resume');
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
if (TERMINAL_JOB_STATUSES.has(state.status)) {
|
|
3851
|
+
throw createHttpError(409, `Job ${jobId} is already ${state.status}`);
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
if (action === 'resume') {
|
|
3855
|
+
const launched = resumeHostedJob(jobId);
|
|
3856
|
+
sendJson(res, 202, {
|
|
3857
|
+
accepted: true,
|
|
3858
|
+
action,
|
|
3859
|
+
jobId,
|
|
3860
|
+
launchMode: launched.launchMode,
|
|
3861
|
+
pid: launched.pid,
|
|
3862
|
+
job: launched.state,
|
|
3863
|
+
});
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
if (IDLE_JOB_STATUSES.has(state.status)) {
|
|
3868
|
+
const job = action === 'pause'
|
|
3869
|
+
? pauseQueuedJob(jobId, parseOptionalObject(body.metadata, 'metadata') || {})
|
|
3870
|
+
: cancelQueuedJob(jobId, parseOptionalObject(body.metadata, 'metadata') || {});
|
|
3871
|
+
sendJson(res, 202, {
|
|
3872
|
+
accepted: true,
|
|
3873
|
+
action,
|
|
3874
|
+
jobId,
|
|
3875
|
+
job,
|
|
3876
|
+
});
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
const metadata = parseOptionalObject(body.metadata, 'metadata') || {};
|
|
3881
|
+
const control = requestJobControl(jobId, action, metadata);
|
|
3882
|
+
sendJson(res, 202, {
|
|
3883
|
+
accepted: true,
|
|
3884
|
+
action,
|
|
3885
|
+
jobId,
|
|
3886
|
+
control,
|
|
3887
|
+
job: readHostedJobOrThrow(jobId),
|
|
3888
|
+
});
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3718
3893
|
if (req.method === 'POST' && pathname === '/v1/gates/constraint') {
|
|
3719
3894
|
const body = await parseJsonBody(req);
|
|
3720
3895
|
if (!body.key || body.value === undefined) {
|
|
@@ -3943,33 +4118,38 @@ async function addContext(){
|
|
|
3943
4118
|
|
|
3944
4119
|
if (req.method === 'POST' && pathname === '/v1/dpo/export') {
|
|
3945
4120
|
const body = await parseJsonBody(req);
|
|
3946
|
-
|
|
4121
|
+
const paths = resolveDpoExportPaths(body, {
|
|
4122
|
+
safeDataDir: requestSafeDataDir,
|
|
4123
|
+
fallbackMemoryLogPath: requestFeedbackPaths.MEMORY_LOG_PATH,
|
|
4124
|
+
});
|
|
4125
|
+
const wantsAsync = body.async === true || normalizeNullableText(body.mode) === 'async';
|
|
3947
4126
|
|
|
3948
|
-
if (
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
4127
|
+
if (wantsAsync) {
|
|
4128
|
+
try {
|
|
4129
|
+
const launched = launchDpoExportJob(paths, {
|
|
4130
|
+
jobId: normalizeNullableText(body.jobId) || undefined,
|
|
4131
|
+
});
|
|
4132
|
+
sendJson(res, 202, {
|
|
4133
|
+
accepted: true,
|
|
4134
|
+
async: true,
|
|
4135
|
+
jobId: launched.jobId,
|
|
4136
|
+
status: launched.state.status,
|
|
4137
|
+
outputPath: paths.outputPath,
|
|
4138
|
+
statusUrl: `/v1/jobs/${encodeURIComponent(launched.jobId)}`,
|
|
4139
|
+
launchMode: launched.launchMode,
|
|
4140
|
+
job: launched.state,
|
|
4141
|
+
});
|
|
4142
|
+
} catch (err) {
|
|
4143
|
+
throw createHttpError(err.statusCode || 400, err.message || 'Invalid DPO export request');
|
|
4144
|
+
}
|
|
4145
|
+
return;
|
|
3964
4146
|
}
|
|
3965
4147
|
|
|
4148
|
+
const memories = loadDpoExportMemories(paths);
|
|
3966
4149
|
const result = exportDpoFromMemories(memories);
|
|
3967
|
-
if (
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
});
|
|
3971
|
-
fs.mkdirSync(path.dirname(safeOutputPath), { recursive: true });
|
|
3972
|
-
fs.writeFileSync(safeOutputPath, result.jsonl);
|
|
4150
|
+
if (paths.outputPath) {
|
|
4151
|
+
fs.mkdirSync(path.dirname(paths.outputPath), { recursive: true });
|
|
4152
|
+
fs.writeFileSync(paths.outputPath, result.jsonl);
|
|
3973
4153
|
}
|
|
3974
4154
|
|
|
3975
4155
|
sendJson(res, 200, {
|
|
@@ -3978,9 +4158,7 @@ async function addContext(){
|
|
|
3978
4158
|
learnings: result.learnings.length,
|
|
3979
4159
|
unpairedErrors: result.unpairedErrors.length,
|
|
3980
4160
|
unpairedLearnings: result.unpairedLearnings.length,
|
|
3981
|
-
outputPath:
|
|
3982
|
-
? resolveSafePath(body.outputPath, { safeDataDir: requestSafeDataDir })
|
|
3983
|
-
: null,
|
|
4161
|
+
outputPath: paths.outputPath,
|
|
3984
4162
|
});
|
|
3985
4163
|
return;
|
|
3986
4164
|
}
|
|
File without changes
|