thumbgate 1.28.4 → 1.29.2
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 +11 -1
- package/.claude/commands/thumbgate-dashboard.md +23 -8
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +18 -10
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +66 -3
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +88 -2
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +8 -8
- package/bin/postinstall.js +4 -13
- package/commands/dashboard.md +11 -1
- package/commands/thumbgate-dashboard.md +23 -8
- package/config/agent-outcome-monitor-thresholds.json +63 -0
- package/config/evals/agent-outcomes-baseline.json +17 -0
- package/config/evals/agent-outcomes-golden.json +412 -0
- package/config/evals/prompt-eval-baseline.json +23 -0
- package/config/github-about.json +5 -4
- package/config/post-deploy-marketing-pages.json +6 -6
- package/config/schemas/task-outcome-receipt.schema.json +296 -0
- package/docs/integrations/grafana/README.md +109 -0
- package/docs/integrations/grafana/thumbgate-revenue-evidence-dashboard.json +1930 -0
- package/openapi/openapi.yaml +475 -5
- package/package.json +75 -22
- package/public/agent-manager.html +10 -11
- package/public/agents-cost-savings.html +2 -2
- package/public/assets/brand/thumbgate-logo-transparent.svg +6 -11
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +11 -10
- package/public/assets/brand/thumbgate-mark.svg +10 -11
- package/public/blog/inside-your-boundary.html +114 -0
- package/public/blog/process-over-outcome-gates.html +119 -0
- package/public/blog.html +296 -402
- package/public/brand/thumbgate-mark.svg +5 -9
- package/public/codex-enterprise.html +2 -2
- package/public/compare.html +12 -3
- package/public/diagnostic.html +79 -29
- package/public/guide.html +4 -4
- package/public/index.html +1090 -2098
- package/public/install.html +3 -3
- package/public/js/buyer-intent.js +33 -18
- package/public/numbers.html +2 -2
- package/public/pricing.html +268 -408
- package/public/pro.html +4 -4
- package/scripts/agent-outcome-eval.js +130 -0
- package/scripts/agent-outcome-monitor.js +261 -0
- package/scripts/agent-reasoning-traces.js +8 -9
- package/scripts/async-job-runner.js +107 -13
- package/scripts/billing.js +456 -126
- package/scripts/buyer-paths.js +102 -0
- package/scripts/cli-feedback.js +2 -2
- package/scripts/commercial-offer.js +18 -10
- package/scripts/durability/step.js +121 -12
- package/scripts/external-customer-audit.js +881 -0
- package/scripts/feedback-loop.js +26 -0
- package/scripts/gates-engine.js +554 -19
- package/scripts/grafana-revenue-evidence.js +856 -0
- package/scripts/human-escalation.js +265 -0
- package/scripts/hybrid-feedback-context.js +93 -50
- package/scripts/jsonl-window.js +89 -0
- package/scripts/judge-reward-function.js +30 -18
- package/scripts/lesson-embedding-index.js +3 -7
- package/scripts/meta-agent-loop.js +20 -2
- package/scripts/observability-env.js +139 -0
- package/scripts/observability-setup.js +55 -0
- package/scripts/plausible-domain-config.js +4 -0
- package/scripts/prompt-eval.js +81 -4
- package/scripts/provider-live-evidence.js +1290 -0
- package/scripts/provider-payment-reconciler.js +442 -0
- package/scripts/provider-revenue-evidence.js +249 -0
- package/scripts/rate-limiter.js +1 -5
- package/scripts/revenue-action-eligibility.js +414 -0
- package/scripts/revenue-evidence-remediation.js +694 -0
- package/scripts/revenue-offer-system.js +709 -0
- package/scripts/sales-pipeline.js +1117 -0
- package/scripts/schedule-manager.js +249 -0
- package/scripts/seo-gsd.js +8 -4
- package/scripts/stripe-credentials.js +37 -0
- package/scripts/stripe-revenue-catalog-audit.js +363 -0
- package/scripts/stripe-revenue-catalog.js +164 -0
- package/scripts/task-outcomes.js +425 -0
- package/scripts/telemetry-analytics.js +23 -3
- package/scripts/tool-contract-validator.js +287 -59
- package/scripts/tool-registry.js +143 -0
- package/scripts/vector-store.js +83 -7
- package/scripts/workflow-intake-queue.js +483 -0
- package/src/api/server.js +647 -118
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_APP_ORIGIN = 'https://thumbgate.ai';
|
|
4
|
+
|
|
5
|
+
function normalizeAppOrigin(value = DEFAULT_APP_ORIGIN) {
|
|
6
|
+
const parsed = new URL(String(value || DEFAULT_APP_ORIGIN));
|
|
7
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
8
|
+
throw new Error('Buyer-path app origin must use http or https.');
|
|
9
|
+
}
|
|
10
|
+
parsed.pathname = '/';
|
|
11
|
+
parsed.search = '';
|
|
12
|
+
parsed.hash = '';
|
|
13
|
+
return parsed.origin;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildBuyerUrl(pathname, params = {}, appOrigin = DEFAULT_APP_ORIGIN) {
|
|
17
|
+
const normalizedOrigin = normalizeAppOrigin(appOrigin);
|
|
18
|
+
const url = new URL(pathname, `${normalizedOrigin}/`);
|
|
19
|
+
if (url.origin !== normalizedOrigin) {
|
|
20
|
+
throw new Error('Buyer URL must remain on the configured first-party origin.');
|
|
21
|
+
}
|
|
22
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
23
|
+
if (value === undefined || value === null || String(value).trim() === '') continue;
|
|
24
|
+
url.searchParams.set(key, String(value).trim());
|
|
25
|
+
}
|
|
26
|
+
return url.toString();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildProBuyerUrl({
|
|
30
|
+
billingCycle = 'monthly',
|
|
31
|
+
appOrigin = DEFAULT_APP_ORIGIN,
|
|
32
|
+
source = null,
|
|
33
|
+
medium = null,
|
|
34
|
+
campaign = null,
|
|
35
|
+
content = null,
|
|
36
|
+
} = {}) {
|
|
37
|
+
return buildBuyerUrl('/go/pro', {
|
|
38
|
+
plan_id: 'pro',
|
|
39
|
+
billing_cycle: billingCycle === 'annual' ? 'annual' : 'monthly',
|
|
40
|
+
utm_source: source,
|
|
41
|
+
utm_medium: medium,
|
|
42
|
+
utm_campaign: campaign,
|
|
43
|
+
utm_content: content,
|
|
44
|
+
}, appOrigin);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildDiagnosticBuyerUrl({
|
|
48
|
+
appOrigin = DEFAULT_APP_ORIGIN,
|
|
49
|
+
source = null,
|
|
50
|
+
medium = null,
|
|
51
|
+
campaign = null,
|
|
52
|
+
content = null,
|
|
53
|
+
} = {}) {
|
|
54
|
+
return buildBuyerUrl('/diagnostic', {
|
|
55
|
+
plan_id: 'sprint_diagnostic',
|
|
56
|
+
utm_source: source,
|
|
57
|
+
utm_medium: medium,
|
|
58
|
+
utm_campaign: campaign,
|
|
59
|
+
utm_content: content,
|
|
60
|
+
}, appOrigin);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildSprintBuyerUrl({
|
|
64
|
+
appOrigin = DEFAULT_APP_ORIGIN,
|
|
65
|
+
source = null,
|
|
66
|
+
medium = null,
|
|
67
|
+
campaign = null,
|
|
68
|
+
content = null,
|
|
69
|
+
} = {}) {
|
|
70
|
+
return buildBuyerUrl('/go/sprint', {
|
|
71
|
+
plan_id: 'workflow_sprint',
|
|
72
|
+
utm_source: source,
|
|
73
|
+
utm_medium: medium,
|
|
74
|
+
utm_campaign: campaign,
|
|
75
|
+
utm_content: content,
|
|
76
|
+
}, appOrigin);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildEnterpriseBuyerUrl({ appOrigin = DEFAULT_APP_ORIGIN } = {}) {
|
|
80
|
+
const url = new URL('/', `${normalizeAppOrigin(appOrigin)}/`);
|
|
81
|
+
url.hash = 'workflow-sprint-intake';
|
|
82
|
+
return url.toString();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isFirstPartyBuyerUrl(value, appOrigin = DEFAULT_APP_ORIGIN) {
|
|
86
|
+
try {
|
|
87
|
+
return new URL(value).origin === normalizeAppOrigin(appOrigin);
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
DEFAULT_APP_ORIGIN,
|
|
95
|
+
buildBuyerUrl,
|
|
96
|
+
buildDiagnosticBuyerUrl,
|
|
97
|
+
buildEnterpriseBuyerUrl,
|
|
98
|
+
buildProBuyerUrl,
|
|
99
|
+
buildSprintBuyerUrl,
|
|
100
|
+
isFirstPartyBuyerUrl,
|
|
101
|
+
normalizeAppOrigin,
|
|
102
|
+
};
|
package/scripts/cli-feedback.js
CHANGED
|
@@ -98,13 +98,13 @@ function formatCliOutput(result) {
|
|
|
98
98
|
const isDown = ['down', 'negative', 'thumbs_down'].includes(feedbackSignal);
|
|
99
99
|
|
|
100
100
|
// Header
|
|
101
|
-
if (feedbackResult?.duplicate) {
|
|
101
|
+
if (feedbackResult?.duplicate && feedbackResult.accepted === true) {
|
|
102
102
|
lines.push(`${Y}${BD}Feedback already captured${RST}`);
|
|
103
103
|
const feedbackId = feedbackResult.feedbackEvent?.id;
|
|
104
104
|
const memoryId = feedbackResult.memoryRecord?.id;
|
|
105
105
|
if (feedbackId) lines.push(`${D} Feedback ID: ${feedbackId}${RST}`);
|
|
106
106
|
if (memoryId) lines.push(`${D} Memory ID : ${memoryId}${RST}`);
|
|
107
|
-
} else if (feedbackResult
|
|
107
|
+
} else if (feedbackResult?.accepted === true) {
|
|
108
108
|
lines.push(`${isDown ? R : G}${BD}${isDown ? '👎 Thumbs down recorded' : '👍 Thumbs up recorded'}${RST}`);
|
|
109
109
|
const feedbackId = feedbackResult.feedbackEvent?.id || feedbackResult.id;
|
|
110
110
|
const memoryId = feedbackResult.memoryRecord?.id || feedbackResult.memoryId;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const { buildProBuyerUrl } = require('./buyer-paths');
|
|
4
|
+
|
|
5
|
+
const PRO_MONTHLY_PAYMENT_LINK = buildProBuyerUrl({
|
|
6
|
+
billingCycle: 'monthly',
|
|
7
|
+
source: 'offer',
|
|
8
|
+
});
|
|
9
|
+
const PRO_ANNUAL_PAYMENT_LINK = buildProBuyerUrl({
|
|
10
|
+
billingCycle: 'annual',
|
|
11
|
+
source: 'offer',
|
|
12
|
+
});
|
|
5
13
|
|
|
6
14
|
const PRO_MONTHLY_PRICE_ID = 'price_1THQY7GGBpd520QYHoS7RG0J';
|
|
7
15
|
const PRO_ANNUAL_PRICE_ID = 'price_1THQZ7GGBpd520QYxzDRnxhB';
|
|
@@ -14,12 +22,11 @@ const TEAM_ANNUAL_PRICE_DOLLARS = 588;
|
|
|
14
22
|
const TEAM_MIN_SEATS = 3;
|
|
15
23
|
|
|
16
24
|
const PRO_PRICE_LABEL = '$19/mo or $149/yr (individual)';
|
|
17
|
-
// Enterprise is the
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const ENTERPRISE_PRICE_LABEL = 'Custom pricing, scoped after intake — Enterprise agent governance';
|
|
25
|
+
// The Enterprise Workflow Gate is the one promoted public service offer. The
|
|
26
|
+
// dormant TEAM_* Stripe constants below remain only as inert billing plumbing
|
|
27
|
+
// pending a dedicated cleanup; they are no longer a customer-facing tier.
|
|
28
|
+
const ENTERPRISE_PRICE_LABEL = 'Enterprise Workflow Gate — $499 one-time for one supported workflow';
|
|
29
|
+
const ENTERPRISE_ENTRY_URL = 'https://thumbgate.ai/pricing';
|
|
23
30
|
|
|
24
31
|
function normalizePlanId(value) {
|
|
25
32
|
const text = String(value || '').trim().toLowerCase();
|
|
@@ -74,7 +81,7 @@ function buildCaptureReceipt({ signal, feedbackId, memoryId, actionType } = {})
|
|
|
74
81
|
` Solo Pro : ${PRO_PRICE_LABEL} for recall, dashboard, adapters, and exports`,
|
|
75
82
|
` Upgrade : ${trackedProUrl('cli_capture_receipt', actionType || normalizedSignal.toLowerCase())}`,
|
|
76
83
|
` Enterprise : ${ENTERPRISE_PRICE_LABEL}; start with one repeated workflow failure`,
|
|
77
|
-
|
|
84
|
+
` ${ENTERPRISE_ENTRY_URL}`,
|
|
78
85
|
'',
|
|
79
86
|
];
|
|
80
87
|
return lines.join('\n');
|
|
@@ -107,7 +114,7 @@ function buildStatsReceipt(stats = {}) {
|
|
|
107
114
|
lines.push(' Show the buyer : npx thumbgate cost');
|
|
108
115
|
lines.push(' Pro recall value : keep lessons/rules searchable, exportable, and visible');
|
|
109
116
|
lines.push(` Solo Pro : ${trackedProUrl('cli_stats_receipt', 'proof_seen')}`);
|
|
110
|
-
lines.push(
|
|
117
|
+
lines.push(` Enterprise : ${ENTERPRISE_ENTRY_URL}`);
|
|
111
118
|
lines.push('');
|
|
112
119
|
return lines.join('\n');
|
|
113
120
|
}
|
|
@@ -125,6 +132,7 @@ module.exports = {
|
|
|
125
132
|
TEAM_MIN_SEATS,
|
|
126
133
|
PRO_PRICE_LABEL,
|
|
127
134
|
ENTERPRISE_PRICE_LABEL,
|
|
135
|
+
ENTERPRISE_ENTRY_URL,
|
|
128
136
|
normalizePlanId,
|
|
129
137
|
normalizeBillingCycle,
|
|
130
138
|
normalizeSeatCount,
|
|
@@ -107,7 +107,34 @@ function errMessage(err) {
|
|
|
107
107
|
return err?.message ?? err;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
function
|
|
110
|
+
function retryAfterMs(err) {
|
|
111
|
+
const raw = err?.retryAfterMs
|
|
112
|
+
?? err?.headers?.['retry-after-ms']
|
|
113
|
+
?? err?.headers?.['retry-after'];
|
|
114
|
+
if (raw === undefined || raw === null || raw === '') return null;
|
|
115
|
+
const numeric = Number(raw);
|
|
116
|
+
if (Number.isFinite(numeric)) {
|
|
117
|
+
return String(raw).includes('.') || err?.headers?.['retry-after'] !== undefined
|
|
118
|
+
? Math.max(0, numeric * 1000)
|
|
119
|
+
: Math.max(0, numeric);
|
|
120
|
+
}
|
|
121
|
+
const dateMs = Date.parse(raw);
|
|
122
|
+
return Number.isNaN(dateMs) ? null : Math.max(0, dateMs - Date.now());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function handleStepError({
|
|
126
|
+
err,
|
|
127
|
+
attempt,
|
|
128
|
+
retries,
|
|
129
|
+
classify,
|
|
130
|
+
backoffMs,
|
|
131
|
+
name,
|
|
132
|
+
onRetry,
|
|
133
|
+
onFail,
|
|
134
|
+
logger,
|
|
135
|
+
jitterRatio,
|
|
136
|
+
randomFn,
|
|
137
|
+
}) {
|
|
111
138
|
const verdict = classify(err);
|
|
112
139
|
const terminal = verdict === 'fail' || attempt >= retries;
|
|
113
140
|
if (terminal) {
|
|
@@ -117,7 +144,11 @@ function handleStepError({ err, attempt, retries, classify, backoffMs, name, onR
|
|
|
117
144
|
}
|
|
118
145
|
return { terminal: true };
|
|
119
146
|
}
|
|
120
|
-
const
|
|
147
|
+
const configuredWait = backoffMs[Math.min(attempt, backoffMs.length - 1)];
|
|
148
|
+
const serverWait = retryAfterMs(err);
|
|
149
|
+
const baseWait = serverWait === null ? configuredWait : Math.max(configuredWait, serverWait);
|
|
150
|
+
const jitter = jitterRatio > 0 ? baseWait * jitterRatio * ((randomFn() * 2) - 1) : 0;
|
|
151
|
+
const waitMs = Math.max(0, Math.round(baseWait + jitter));
|
|
121
152
|
if (typeof onRetry === 'function') onRetry({ name, attempt, err, waitMs, verdict });
|
|
122
153
|
if (typeof logger === 'function') {
|
|
123
154
|
logger(`[step:${name}] RETRY attempt=${attempt} waitMs=${waitMs} err=${errMessage(err)}`);
|
|
@@ -126,10 +157,9 @@ function handleStepError({ err, attempt, retries, classify, backoffMs, name, onR
|
|
|
126
157
|
}
|
|
127
158
|
|
|
128
159
|
async function runStep(name, options, fn) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
160
|
+
const invocation = normalizeStepInvocation(options, fn);
|
|
161
|
+
options = invocation.options;
|
|
162
|
+
fn = invocation.fn;
|
|
133
163
|
const {
|
|
134
164
|
retries = 3,
|
|
135
165
|
backoffMs = DEFAULT_BACKOFF_MS,
|
|
@@ -139,33 +169,112 @@ async function runStep(name, options, fn) {
|
|
|
139
169
|
onFail,
|
|
140
170
|
logger,
|
|
141
171
|
sleepFn = sleep,
|
|
172
|
+
sideEffect = false,
|
|
173
|
+
idempotencyKey: stepIdempotencyKey,
|
|
174
|
+
maxElapsedMs = Infinity,
|
|
175
|
+
jitterRatio = 0,
|
|
176
|
+
randomFn = Math.random,
|
|
142
177
|
} = options || {};
|
|
143
178
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
validateStepConfiguration(name, fn, {
|
|
180
|
+
sideEffect,
|
|
181
|
+
stepIdempotencyKey,
|
|
182
|
+
maxElapsedMs,
|
|
183
|
+
});
|
|
147
184
|
|
|
148
185
|
let lastErr;
|
|
186
|
+
const startedAt = Date.now();
|
|
149
187
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
150
|
-
|
|
188
|
+
const context = {
|
|
189
|
+
name,
|
|
190
|
+
attempt,
|
|
191
|
+
idempotencyKey: stepIdempotencyKey || null,
|
|
192
|
+
elapsedMs: Date.now() - startedAt,
|
|
193
|
+
};
|
|
194
|
+
callIfFunction(onAttempt, context);
|
|
151
195
|
try {
|
|
152
|
-
return await fn(
|
|
196
|
+
return await fn(context);
|
|
153
197
|
} catch (err) {
|
|
154
198
|
lastErr = err;
|
|
155
199
|
const outcome = handleStepError({
|
|
156
|
-
err,
|
|
200
|
+
err,
|
|
201
|
+
attempt,
|
|
202
|
+
retries,
|
|
203
|
+
classify,
|
|
204
|
+
backoffMs,
|
|
205
|
+
name,
|
|
206
|
+
onRetry,
|
|
207
|
+
onFail,
|
|
208
|
+
logger,
|
|
209
|
+
jitterRatio: Math.max(0, Number(jitterRatio) || 0),
|
|
210
|
+
randomFn,
|
|
157
211
|
});
|
|
158
212
|
if (outcome.terminal) throw err;
|
|
213
|
+
enforceRetryBudget({
|
|
214
|
+
err,
|
|
215
|
+
attempt,
|
|
216
|
+
name,
|
|
217
|
+
onFail,
|
|
218
|
+
startedAt,
|
|
219
|
+
waitMs: outcome.waitMs,
|
|
220
|
+
maxElapsedMs,
|
|
221
|
+
});
|
|
159
222
|
await sleepFn(outcome.waitMs);
|
|
160
223
|
}
|
|
161
224
|
}
|
|
162
225
|
throw lastErr;
|
|
163
226
|
}
|
|
164
227
|
|
|
228
|
+
function normalizeStepInvocation(options, fn) {
|
|
229
|
+
if (typeof options === 'function') return { options: {}, fn: options };
|
|
230
|
+
return { options: options || {}, fn };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function validateStepConfiguration(name, fn, options) {
|
|
234
|
+
if (typeof fn !== 'function') {
|
|
235
|
+
throw new TypeError(`runStep(${name}): fn must be a function`);
|
|
236
|
+
}
|
|
237
|
+
if (options.sideEffect && !String(options.stepIdempotencyKey || '').trim()) {
|
|
238
|
+
const error = new Error(`runStep(${name}): side-effecting steps require idempotencyKey`);
|
|
239
|
+
error.code = 'THUMBGATE_IDEMPOTENCY_KEY_REQUIRED';
|
|
240
|
+
error.nonRetryable = true;
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
if (!Number.isFinite(Number(options.maxElapsedMs)) && options.maxElapsedMs !== Infinity) {
|
|
244
|
+
throw new TypeError(`runStep(${name}): maxElapsedMs must be finite or Infinity`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function callIfFunction(callback, payload) {
|
|
249
|
+
if (typeof callback === 'function') callback(payload);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function enforceRetryBudget({
|
|
253
|
+
err,
|
|
254
|
+
attempt,
|
|
255
|
+
name,
|
|
256
|
+
onFail,
|
|
257
|
+
startedAt,
|
|
258
|
+
waitMs,
|
|
259
|
+
maxElapsedMs,
|
|
260
|
+
}) {
|
|
261
|
+
if (Date.now() - startedAt + waitMs <= maxElapsedMs) return;
|
|
262
|
+
err.code = err.code || 'THUMBGATE_RETRY_BUDGET_EXHAUSTED';
|
|
263
|
+
err.retryBudgetExhausted = true;
|
|
264
|
+
callIfFunction(onFail, {
|
|
265
|
+
name,
|
|
266
|
+
attempt,
|
|
267
|
+
err,
|
|
268
|
+
verdict: 'retry_budget_exhausted',
|
|
269
|
+
});
|
|
270
|
+
throw err;
|
|
271
|
+
}
|
|
272
|
+
|
|
165
273
|
module.exports = {
|
|
166
274
|
runStep,
|
|
167
275
|
idempotencyKey,
|
|
168
276
|
defaultClassify,
|
|
169
277
|
TRANSIENT_CODES,
|
|
170
278
|
DEFAULT_BACKOFF_MS,
|
|
279
|
+
retryAfterMs,
|
|
171
280
|
};
|