thumbgate 1.21.1 → 1.22.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.
@@ -7,6 +7,7 @@
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
8
8
  case "$SCRIPT_DIR" in *[!a-zA-Z0-9/_.-]*) echo "ThumbGate: invalid script path"; exit 1;; esac
9
9
  LOCAL_API_ORIGIN="${THUMBGATE_LOCAL_API_ORIGIN:-http://localhost:3456}"
10
+ STATUSLINE_VERBOSE="${THUMBGATE_STATUSLINE_VERBOSE:-0}"
10
11
 
11
12
  # ── Parse Claude Code session JSON from stdin ─────────────────────
12
13
  eval "$(cat | jq -r '
@@ -92,17 +93,19 @@ fi
92
93
  LINK_STATE="offline"
93
94
  UP_URL=""; DOWN_URL=""; DASHBOARD_URL=""; LESSONS_URL=""
94
95
  DASHBOARD_LABEL="Dashboard"; LESSONS_LABEL="Lessons"
95
- _LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
96
- if [ -n "$_LINKS_JSON" ]; then
97
- eval "$(echo "$_LINKS_JSON" | jq -r '
98
- @sh "LINK_STATE=\(.state // "offline")",
99
- @sh "UP_URL=\(.upUrl // "")",
100
- @sh "DOWN_URL=\(.downUrl // "")",
101
- @sh "DASHBOARD_URL=\(.dashboardUrl // "")",
102
- @sh "LESSONS_URL=\(.lessonsUrl // "")",
103
- @sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
104
- @sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
105
- ' 2>/dev/null)"
96
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
97
+ _LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
98
+ if [ -n "$_LINKS_JSON" ]; then
99
+ eval "$(echo "$_LINKS_JSON" | jq -r '
100
+ @sh "LINK_STATE=\(.state // "offline")",
101
+ @sh "UP_URL=\(.upUrl // "")",
102
+ @sh "DOWN_URL=\(.downUrl // "")",
103
+ @sh "DASHBOARD_URL=\(.dashboardUrl // "")",
104
+ @sh "LESSONS_URL=\(.lessonsUrl // "")",
105
+ @sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
106
+ @sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
107
+ ' 2>/dev/null)"
108
+ fi
106
109
  fi
107
110
 
108
111
  # ── ThumbGate package metadata ────────────────────────────────────────
@@ -117,13 +120,15 @@ fi
117
120
 
118
121
  # ── Repo context (branch / work item / PR) ───────────────────────────
119
122
  BRANCH_NAME=""; WORK_ITEM_LABEL=""; PR_LABEL=""
120
- _CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
121
- if [[ -n "$_CONTEXT_JSON" ]]; then
122
- eval "$(echo "$_CONTEXT_JSON" | jq -r '
123
- @sh "BRANCH_NAME=\(.branchName // "")",
124
- @sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
125
- @sh "PR_LABEL=\(.prLabel // "")"
126
- ' 2>/dev/null)"
123
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
124
+ _CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
125
+ if [[ -n "$_CONTEXT_JSON" ]]; then
126
+ eval "$(echo "$_CONTEXT_JSON" | jq -r '
127
+ @sh "BRANCH_NAME=\(.branchName // "")",
128
+ @sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
129
+ @sh "PR_LABEL=\(.prLabel // "")"
130
+ ' 2>/dev/null)"
131
+ fi
127
132
  fi
128
133
 
129
134
  # ── Control Tower stats ──────────────────────────────────────────
@@ -139,14 +144,16 @@ fi
139
144
 
140
145
  # ── Latest lesson (data available for extensions; not rendered in statusbar) ──
141
146
  LESSON_TEXT=""; LESSON_ID=""; LESSON_LABEL=""; LESSON_LINK=""
142
- _LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
143
- if [[ -n "$_LESSON_JSON" ]]; then
144
- eval "$(echo "$_LESSON_JSON" | jq -r '
145
- @sh "LESSON_TEXT=\(.text // "")",
146
- @sh "LESSON_ID=\(.lessonId // "")",
147
- @sh "LESSON_LABEL=\(.label // "")",
148
- @sh "LESSON_LINK=\(.link // "")"
149
- ' 2>/dev/null)"
147
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
148
+ _LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
149
+ if [[ -n "$_LESSON_JSON" ]]; then
150
+ eval "$(echo "$_LESSON_JSON" | jq -r '
151
+ @sh "LESSON_TEXT=\(.text // "")",
152
+ @sh "LESSON_ID=\(.lessonId // "")",
153
+ @sh "LESSON_LABEL=\(.label // "")",
154
+ @sh "LESSON_LINK=\(.link // "")"
155
+ ' 2>/dev/null)"
156
+ fi
150
157
  fi
151
158
 
152
159
  # ── Colors ────────────────────────────────────────────────────────
@@ -199,8 +206,10 @@ LINE="${LINE:+${LINE} · }ThumbGate v${TG_VERSION} · ${TG_TIER}"
199
206
  if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
200
207
  LINE="${D}${LINE}${RST} · no feedback yet"
201
208
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
202
- LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
203
- [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
209
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
210
+ LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
211
+ [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
212
+ fi
204
213
  printf '%b\n' "$LINE"
205
214
  else
206
215
  LINE="${LINE} · ${G}${BD}${UP}${RST}${UP_LINK} ${R}${BD}${DOWN}${RST}${DOWN_LINK} ${ARROW}"
@@ -210,8 +219,10 @@ else
210
219
  [[ "${AT_RISK:-0}" -gt 0 ]] && LINE="${LINE} ${R}${AT_RISK}⚠${RST}"
211
220
  [[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}☠${RST}"
212
221
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
213
- LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
214
- [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
222
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
223
+ LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
224
+ [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
225
+ fi
215
226
 
216
227
  printf '%b\n' "$LINE"
217
228
  fi
@@ -397,6 +397,24 @@ const TOOLS = [
397
397
  },
398
398
  },
399
399
  }),
400
+ readOnlyTool({
401
+ name: 'suggest_fix',
402
+ description: 'Suggest corrective actions for a described failure by searching the lesson DB and prevention rules. Returns up to 3 ranked suggestions with their source. Call this when something goes wrong and you need guidance on what to do next.',
403
+ inputSchema: {
404
+ type: 'object',
405
+ required: ['context'],
406
+ properties: {
407
+ context: {
408
+ type: 'string',
409
+ description: 'Description of what went wrong or what the agent is trying to fix.',
410
+ },
411
+ limit: {
412
+ type: 'number',
413
+ description: 'Maximum number of suggestions to return (default 3, max 5).',
414
+ },
415
+ },
416
+ },
417
+ }),
400
418
  readOnlyTool({
401
419
  name: 'infer_lesson_from_history',
402
420
  description: 'Perform autonomous inference on chat history to identify why a failure occurred and what rule should be recorded.',
package/src/api/server.js CHANGED
@@ -2504,6 +2504,7 @@ function renderSitemapXml(runtimeConfig) {
2504
2504
  { path: '/agent-manager', changefreq: 'weekly', priority: '0.9' },
2505
2505
  { path: '/llm-context.md', changefreq: 'weekly', priority: '0.8' },
2506
2506
  { path: '/codex-plugin', changefreq: 'weekly', priority: '0.75' },
2507
+ { path: '/codex-enterprise', changefreq: 'weekly', priority: '0.85' },
2507
2508
  ...THUMBGATE_SEO_SITEMAP_ENTRIES,
2508
2509
  ];
2509
2510
  return [
@@ -4520,6 +4521,30 @@ async function addContext(){
4520
4521
  return;
4521
4522
  }
4522
4523
 
4524
+ if (isGetLikeRequest && (pathname === '/codex-enterprise' || pathname === '/codex-enterprise.html')) {
4525
+ // Landing page riding the 2026-05-20 OpenAI×Dell Codex Enterprise
4526
+ // partnership announcement. Dell-distributed Codex expands the TAM
4527
+ // for ThumbGate's governance layer — capture every agent decision,
4528
+ // promote repeat failures to PreToolUse gates, ship the audit trail
4529
+ // procurement requires. Routed through servePublicMarketingPage so
4530
+ // arrivals via the partnership news cycle capture UTM attribution
4531
+ // and landing_page_view telemetry with pageType: 'codex_enterprise'.
4532
+ try {
4533
+ servePublicMarketingPage({
4534
+ req,
4535
+ res,
4536
+ parsed,
4537
+ hostedConfig,
4538
+ isHeadRequest,
4539
+ renderHtml: () => fs.readFileSync(path.join(PUBLIC_DIR, 'codex-enterprise.html'), 'utf-8'),
4540
+ extraTelemetry: { pageType: 'codex_enterprise' },
4541
+ });
4542
+ } catch {
4543
+ sendJson(res, 404, { error: 'Codex Enterprise page not found' });
4544
+ }
4545
+ return;
4546
+ }
4547
+
4523
4548
  if (isGetLikeRequest && pathname === '/learn/learn.css') {
4524
4549
  try {
4525
4550
  const cssPath = path.join(LEARN_DIR, 'learn.css');
@@ -7729,6 +7754,7 @@ function startServer({ port, host } = {}) {
7729
7754
  const listenPort = Number(port ?? process.env.PORT ?? 8787);
7730
7755
  const listenHost = String(host ?? process.env.HOST ?? '0.0.0.0').trim() || '0.0.0.0';
7731
7756
  const server = createApiServer();
7757
+ registerGracefulShutdown(server);
7732
7758
  return new Promise((resolve) => {
7733
7759
  server.listen(listenPort, listenHost, () => {
7734
7760
  const address = server.address();
@@ -7744,6 +7770,39 @@ function startServer({ port, host } = {}) {
7744
7770
  });
7745
7771
  }
7746
7772
 
7773
+ // Railway / Cloud Run / Kubernetes deploy rotations send SIGTERM to swap
7774
+ // containers. Without a handler, Node exits immediately — in-flight requests
7775
+ // are killed and the orchestrator may mark the container as "crashed" (instead
7776
+ // of "gracefully stopped"), wasting its restart-policy budget on a healthy
7777
+ // shutdown. Drain HTTP, give a deadline, then force-exit if anything hangs.
7778
+ function registerGracefulShutdown(server, { gracePeriodMs = 25_000 } = {}) {
7779
+ if (server[GRACEFUL_SHUTDOWN_KEY]) return;
7780
+ server[GRACEFUL_SHUTDOWN_KEY] = true;
7781
+ let shuttingDown = false;
7782
+ const stop = (signal) => {
7783
+ if (shuttingDown) return;
7784
+ shuttingDown = true;
7785
+ console.log(`[shutdown] ${signal} received — draining connections (deadline ${gracePeriodMs}ms)`);
7786
+ const forceTimer = setTimeout(() => {
7787
+ console.error('[shutdown] grace period elapsed — forcing exit');
7788
+ process.exit(1);
7789
+ }, gracePeriodMs);
7790
+ if (typeof forceTimer.unref === 'function') forceTimer.unref();
7791
+ server.close((err) => {
7792
+ if (err) {
7793
+ console.error('[shutdown] server.close error:', err.message);
7794
+ process.exit(1);
7795
+ }
7796
+ console.log('[shutdown] drained cleanly');
7797
+ process.exit(0);
7798
+ });
7799
+ };
7800
+ process.on('SIGTERM', () => stop('SIGTERM'));
7801
+ process.on('SIGINT', () => stop('SIGINT'));
7802
+ }
7803
+
7804
+ const GRACEFUL_SHUTDOWN_KEY = Symbol.for('thumbgate.gracefulShutdownRegistered');
7805
+
7747
7806
  module.exports = {
7748
7807
  createApiServer,
7749
7808
  startServer,