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.
Files changed (129) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +59 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +210 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +157 -8
  12. package/config/evals/agent-safety-eval.json +338 -22
  13. package/config/gates/routine.json +43 -0
  14. package/config/github-about.json +3 -3
  15. package/config/model-candidates.json +131 -0
  16. package/openapi/openapi.yaml +118 -2
  17. package/package.json +57 -49
  18. package/public/blog.html +7 -7
  19. package/public/codex-plugin.html +6 -6
  20. package/public/compare.html +29 -23
  21. package/public/dashboard.html +82 -10
  22. package/public/guide.html +28 -28
  23. package/public/index.html +216 -98
  24. package/public/learn.html +50 -22
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +17 -17
  27. package/public/pro.html +82 -18
  28. package/scripts/agent-audit-trace.js +55 -0
  29. package/scripts/agent-memory-lifecycle.js +96 -0
  30. package/scripts/agent-readiness-plan.js +118 -0
  31. package/scripts/agentic-data-pipeline.js +21 -1
  32. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  33. package/scripts/ai-org-governance.js +98 -0
  34. package/scripts/ai-search-distribution.js +43 -0
  35. package/scripts/artifact-agent-plan.js +81 -0
  36. package/scripts/billing.js +27 -8
  37. package/scripts/cli-schema.js +18 -2
  38. package/scripts/code-mode-mcp-plan.js +71 -0
  39. package/scripts/context-engine.js +1 -2
  40. package/scripts/context-manager.js +4 -1
  41. package/scripts/dashboard-render-spec.js +1 -1
  42. package/scripts/dashboard.js +275 -9
  43. package/scripts/decision-journal.js +13 -3
  44. package/scripts/document-workflow-governance.js +62 -0
  45. package/scripts/enterprise-agent-rollout.js +34 -0
  46. package/scripts/experience-replay-governance.js +69 -0
  47. package/scripts/export-hf-dataset.js +1 -1
  48. package/scripts/feedback-loop.js +92 -4
  49. package/scripts/feedback-to-rules.js +17 -23
  50. package/scripts/gates-engine.js +4 -6
  51. package/scripts/growth-campaigns.js +49 -0
  52. package/scripts/harness-selector.js +16 -4
  53. package/scripts/hybrid-supervisor-agent.js +64 -0
  54. package/scripts/inference-cache-policy.js +72 -0
  55. package/scripts/inference-economics.js +53 -0
  56. package/scripts/internal-agent-bootstrap.js +12 -2
  57. package/scripts/knowledge-layer-plan.js +108 -0
  58. package/scripts/lesson-inference.js +183 -44
  59. package/scripts/lesson-search.js +4 -1
  60. package/scripts/llm-client.js +157 -26
  61. package/scripts/mailer/resend-mailer.js +112 -1
  62. package/scripts/mcp-transport-strategy.js +66 -0
  63. package/scripts/memory-store-governance.js +60 -0
  64. package/scripts/meta-agent-loop.js +7 -13
  65. package/scripts/model-access-eligibility.js +38 -0
  66. package/scripts/model-migration-readiness.js +55 -0
  67. package/scripts/operational-integrity.js +96 -3
  68. package/scripts/otel-declarative-config.js +56 -0
  69. package/scripts/perplexity-client.js +1 -1
  70. package/scripts/post-training-governance.js +34 -0
  71. package/scripts/private-core-boundary.js +72 -0
  72. package/scripts/production-agent-readiness.js +40 -0
  73. package/scripts/prompt-eval.js +564 -32
  74. package/scripts/prompt-programs.js +93 -0
  75. package/scripts/provider-action-normalizer.js +585 -0
  76. package/scripts/scaling-law-claims.js +60 -0
  77. package/scripts/security-scanner.js +1 -1
  78. package/scripts/self-distill-agent.js +7 -32
  79. package/scripts/seo-gsd.js +232 -55
  80. package/scripts/skill-rag-router.js +53 -0
  81. package/scripts/spec-gate.js +1 -1
  82. package/scripts/student-consistent-training.js +73 -0
  83. package/scripts/synthetic-data-provenance.js +98 -0
  84. package/scripts/task-context-result.js +81 -0
  85. package/scripts/telemetry-analytics.js +149 -0
  86. package/scripts/thompson-sampling.js +2 -2
  87. package/scripts/token-savings.js +7 -6
  88. package/scripts/token-tco.js +46 -0
  89. package/scripts/tool-registry.js +63 -3
  90. package/scripts/verification-loop.js +10 -1
  91. package/scripts/verifier-scoring.js +71 -0
  92. package/scripts/workflow-sentinel.js +284 -28
  93. package/scripts/workspace-agent-routines.js +118 -0
  94. package/src/api/server.js +381 -120
  95. package/scripts/analytics-report.js +0 -328
  96. package/scripts/autonomous-workflow.js +0 -377
  97. package/scripts/billing-setup.js +0 -109
  98. package/scripts/creator-campaigns.js +0 -239
  99. package/scripts/cross-encoder-reranker.js +0 -235
  100. package/scripts/daemon-manager.js +0 -108
  101. package/scripts/decision-trace.js +0 -354
  102. package/scripts/delegation-runtime.js +0 -896
  103. package/scripts/dispatch-brief.js +0 -159
  104. package/scripts/distribution-surfaces.js +0 -110
  105. package/scripts/feedback-history-distiller.js +0 -382
  106. package/scripts/funnel-analytics.js +0 -35
  107. package/scripts/history-distiller.js +0 -200
  108. package/scripts/hosted-job-launcher.js +0 -256
  109. package/scripts/intent-router.js +0 -392
  110. package/scripts/lesson-reranker.js +0 -263
  111. package/scripts/lesson-retrieval.js +0 -148
  112. package/scripts/managed-lesson-agent.js +0 -183
  113. package/scripts/operational-dashboard.js +0 -103
  114. package/scripts/operational-summary.js +0 -129
  115. package/scripts/operator-artifacts.js +0 -608
  116. package/scripts/optimize-context.js +0 -17
  117. package/scripts/org-dashboard.js +0 -206
  118. package/scripts/partner-orchestration.js +0 -146
  119. package/scripts/predictive-insights.js +0 -356
  120. package/scripts/pulse.js +0 -80
  121. package/scripts/reflector-agent.js +0 -221
  122. package/scripts/sales-pipeline.js +0 -681
  123. package/scripts/session-episode-store.js +0 -329
  124. package/scripts/session-health-sensor.js +0 -242
  125. package/scripts/session-report.js +0 -120
  126. package/scripts/swarm-coordinator.js +0 -81
  127. package/scripts/tool-kpi-tracker.js +0 -12
  128. package/scripts/webhook-delivery.js +0 -62
  129. package/scripts/workflow-sprint-intake.js +0 -475
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * History Distiller — Self-Healing Brain for ThumbGate (Pro feature).
6
- *
7
- * When a user gives thumbs-down, this module:
8
- * 1. Takes the last N conversation messages (chatHistory)
9
- * 2. Identifies the failed tool call and its context
10
- * 3. Auto-proposes a lesson: "I noticed X. I've recorded a rule to NEVER do X. Correct?"
11
- * 4. Creates a prevention rule if the user confirms
12
- *
13
- * This closes the gap from "manual guardrail" to "self-healing brain."
14
- * Strategic value: justifies Pro $19/mo subscription via outcome-based intelligence.
15
- */
16
-
17
- const { createLesson, inferFromSurroundingMessages } = require('./lesson-inference');
18
- const contextfs = require('./contextfs');
19
-
20
- // ---------------------------------------------------------------------------
21
- // Chat History Analysis
22
- // ---------------------------------------------------------------------------
23
-
24
-
25
- const ANTI_PATTERNS = [
26
- { pattern: /\b(?:tailwind|tw-)\b/i, label: 'Tailwind CSS', ruleTemplate: 'NEVER use Tailwind CSS in this project' },
27
- { pattern: /(?:force[- ]?push|push\s*--force|--force)\b/i, label: 'force push', ruleTemplate: 'NEVER force-push to any branch' },
28
- { pattern: /\b(?:rm\s+-rf|delete\s+all)\b/i, label: 'destructive deletion', ruleTemplate: 'NEVER run destructive delete commands without confirmation' },
29
- { pattern: /\bskip\s*(?:test|ci|check)/i, label: 'skipping tests', ruleTemplate: 'NEVER skip tests or CI checks' },
30
- { pattern: /\b(?:mock(?:ed|ing)?|stub(?:bed|bing)?)\b.*\b(?:database|db)\b/i, label: 'mocking database', ruleTemplate: 'NEVER mock the database — use real test instances' },
31
- { pattern: /\b(?:hardcod|hard[- ]cod)/i, label: 'hardcoded values', ruleTemplate: 'NEVER hardcode secrets, URLs, or configuration values' },
32
- { pattern: /\b(?:console\.log|print\s+debug)\b/i, label: 'debug logging', ruleTemplate: 'NEVER leave debug console.log/print statements in production code' },
33
- { pattern: /\b(?:any\b.*type|:\s*any\b)/i, label: 'TypeScript any', ruleTemplate: 'NEVER use the `any` type — use proper type annotations' },
34
- ];
35
-
36
- /**
37
- * Analyze chat history to find the correction pattern.
38
- * Looks for: user corrected the agent about something → agent did it again → user gave thumbs down.
39
- *
40
- * @param {Array} chatHistory - Array of {role, content} messages, most recent last
41
- * @param {Object} failedToolCall - The tool call that triggered the thumbs-down
42
- * @returns {{ correction, antiPattern, proposedRule, confidence, evidence }}
43
- */
44
- function analyzeChatHistory(chatHistory, failedToolCall = null) {
45
- const messages = Array.isArray(chatHistory) ? chatHistory : [];
46
- if (messages.length === 0) return { correction: null, antiPattern: null, proposedRule: null, confidence: 0, evidence: [] };
47
-
48
- const evidence = [];
49
- let bestAntiPattern = null;
50
- let userCorrection = null;
51
-
52
- // Scan for user corrections (messages where user said "don't", "stop", "no", "never", "wrong")
53
- const correctionPatterns = [
54
- /\b(?:don'?t|do not|stop|never|wrong|no,?\s+(?:that|not|I said))\b/i,
55
- /\b(?:I (?:told|said|asked) you|I already|we don'?t)\b/i,
56
- /\b(?:should(?:n'?t| not)|must not|not supposed to)\b/i,
57
- ];
58
-
59
- for (const msg of messages) {
60
- if (msg.role !== 'user') continue;
61
- const text = msg.content || msg.text || '';
62
- for (const cp of correctionPatterns) {
63
- if (cp.test(text)) {
64
- userCorrection = text.slice(0, 200);
65
- evidence.push({ type: 'user_correction', text: userCorrection });
66
- break;
67
- }
68
- }
69
- }
70
-
71
- // Scan assistant messages for anti-patterns
72
- const assistantMessages = messages.filter((m) => m.role === 'assistant').map((m) => m.content || m.text || '');
73
- const allAssistantText = assistantMessages.join('\n');
74
-
75
- // Also check the failed tool call
76
- const toolCallText = failedToolCall
77
- ? `${failedToolCall.tool || ''} ${failedToolCall.input || ''} ${failedToolCall.output || ''}`
78
- : '';
79
- const combinedText = `${allAssistantText}\n${toolCallText}`;
80
-
81
- for (const ap of ANTI_PATTERNS) {
82
- if (ap.pattern.test(combinedText)) {
83
- bestAntiPattern = ap;
84
- evidence.push({ type: 'anti_pattern', label: ap.label });
85
- break;
86
- }
87
- }
88
-
89
- // Build proposed rule
90
- let proposedRule = null;
91
- let confidence = 0;
92
-
93
- if (bestAntiPattern && userCorrection) {
94
- proposedRule = bestAntiPattern.ruleTemplate;
95
- confidence = 90;
96
- } else if (bestAntiPattern) {
97
- proposedRule = bestAntiPattern.ruleTemplate;
98
- confidence = 60;
99
- } else if (userCorrection) {
100
- // Extract the "don't X" part as a rule
101
- const dontMatch = userCorrection.match(/(?:don'?t|never|stop)\s+(.{5,60})/i);
102
- if (dontMatch) {
103
- proposedRule = `NEVER ${dontMatch[1].trim().replace(/[.!?]+$/, '')}`;
104
- confidence = 50;
105
- }
106
- }
107
-
108
- return {
109
- correction: userCorrection,
110
- antiPattern: bestAntiPattern ? bestAntiPattern.label : null,
111
- proposedRule,
112
- confidence,
113
- evidence,
114
- messageCount: messages.length,
115
- };
116
- }
117
-
118
- // ---------------------------------------------------------------------------
119
- // History-Aware Distillation (the main entry point)
120
- // ---------------------------------------------------------------------------
121
-
122
- /**
123
- * Distill a lesson from chat history when thumbs-down is received.
124
- * This is the "Reflector" agent — it takes conversation context and
125
- * auto-proposes what went wrong + a prevention rule.
126
- *
127
- * @param {Object} opts
128
- * @param {Array} opts.chatHistory - Last N messages [{role, content}]
129
- * @param {Object} [opts.failedToolCall] - The tool call that failed
130
- * @param {string} [opts.feedbackContext] - User's feedback context string
131
- * @param {string} [opts.signal] - 'negative' or 'positive'
132
- * @returns {{ lesson, proposedRule, confirmation, autoCreated }}
133
- */
134
- function distillFromHistory({ chatHistory = [], failedToolCall = null, feedbackContext = '', signal = 'negative' } = {}) {
135
- // Step 1: Analyze chat history for corrections and anti-patterns
136
- const analysis = analyzeChatHistory(chatHistory, failedToolCall);
137
-
138
- // Step 2: Infer from surrounding messages (leverage existing module)
139
- const inference = inferFromSurroundingMessages({
140
- priorMessages: chatHistory.slice(-10),
141
- signal,
142
- feedbackContext,
143
- });
144
-
145
- // Step 3: Build the auto-proposed lesson
146
- let proposedWhatWentWrong;
147
- let proposedRule = analysis.proposedRule;
148
-
149
- if (analysis.correction && analysis.antiPattern) {
150
- proposedWhatWentWrong = `I noticed I used ${analysis.antiPattern} despite your earlier correction: "${analysis.correction.slice(0, 100)}". This has been recorded as a mistake.`;
151
- } else if (analysis.antiPattern) {
152
- proposedWhatWentWrong = `I detected a ${analysis.antiPattern} anti-pattern in my output that likely caused the issue.`;
153
- } else if (analysis.correction) {
154
- proposedWhatWentWrong = `You previously corrected me: "${analysis.correction.slice(0, 100)}". I may have repeated the same mistake.`;
155
- } else if (inference.inferredAction) {
156
- proposedWhatWentWrong = `The ${inference.inferredAction.type} action on ${inference.inferredAction.target} did not produce the expected result.`;
157
- } else {
158
- proposedWhatWentWrong = feedbackContext || 'The agent action did not meet expectations.';
159
- }
160
-
161
- // Step 4: Build confirmation message
162
- const confirmation = proposedRule
163
- ? `I've recorded a rule: "${proposedRule}". Correct?`
164
- : `Lesson captured: "${proposedWhatWentWrong.slice(0, 100)}". Any corrections?`;
165
-
166
- // Step 5: Auto-create the lesson
167
- const lesson = createLesson({
168
- signal,
169
- inferredLesson: proposedWhatWentWrong,
170
- triggerMessage: inference.triggerMessage,
171
- priorSummary: inference.priorSummary,
172
- confidence: analysis.confidence || inference.confidence,
173
- tags: analysis.antiPattern ? [analysis.antiPattern] : [],
174
- metadata: { distilled: true, hasCorrection: !!analysis.correction, hasAntiPattern: !!analysis.antiPattern },
175
- });
176
-
177
- // Step 6: If high confidence + anti-pattern, auto-install prevention rule
178
- let ruleInstalled = false;
179
- if (proposedRule && analysis.confidence >= 60) {
180
- try {
181
- contextfs.registerPreventionRules(`# Auto-Distilled Rule\n\n- ${proposedRule}\n\nSource: history-distiller (confidence: ${analysis.confidence}%)`, { source: 'history-distiller', lessonId: lesson.id });
182
- ruleInstalled = true;
183
- } catch { /* non-critical */ }
184
- }
185
-
186
- return {
187
- lesson,
188
- proposedWhatWentWrong,
189
- proposedRule,
190
- confirmation,
191
- ruleInstalled,
192
- analysis,
193
- inference: { action: inference.inferredAction, confidence: inference.confidence },
194
- autoCreated: true,
195
- };
196
- }
197
-
198
- module.exports = {
199
- ANTI_PATTERNS, analyzeChatHistory, distillFromHistory,
200
- };
@@ -1,256 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { spawn } = require('child_process');
6
-
7
- const runner = require('./async-job-runner');
8
- const { buildHarnessJob } = require('./natural-language-harness');
9
- const { ensureDir } = require('./fs-utils');
10
-
11
- const RUNNER_SCRIPT_PATH = path.join(__dirname, 'async-job-runner.js');
12
- const MANAGED_DPO_EXPORT_SCRIPT_PATH = path.join(__dirname, 'managed-dpo-export.js');
13
- const BACKGROUND_LAUNCH_MODE = 'background';
14
- const INLINE_LAUNCH_MODE = 'inline';
15
- const IDLE_JOB_STATUSES = new Set(['queued', 'paused', 'resume_requested']);
16
-
17
- function nowIso() {
18
- return new Date().toISOString();
19
- }
20
-
21
-
22
- function shellQuote(value) {
23
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
24
- }
25
-
26
- function createHostedJobId(prefix = 'job') {
27
- return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
28
- }
29
-
30
- function getStatePath(jobId) {
31
- return runner.getJobRuntimePaths(jobId).statePath;
32
- }
33
-
34
- function writeStateFile(jobId, state) {
35
- const statePath = getStatePath(jobId);
36
- ensureDir(path.dirname(statePath));
37
- fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
38
- return state;
39
- }
40
-
41
- function updateIdleJobState(jobId, updater) {
42
- const state = runner.readJobState(jobId);
43
- if (!state) {
44
- const error = new Error(`No persisted state found for job ${jobId}`);
45
- error.statusCode = 404;
46
- throw error;
47
- }
48
-
49
- if (!IDLE_JOB_STATUSES.has(state.status)) {
50
- const error = new Error(`Job ${jobId} is not idle; current status is ${state.status}`);
51
- error.statusCode = 409;
52
- throw error;
53
- }
54
-
55
- return writeStateFile(jobId, updater({ ...state }));
56
- }
57
-
58
- function writeJobFile(jobId, jobSpec) {
59
- const { jobDir } = runner.getJobRuntimePaths(jobId);
60
- ensureDir(jobDir);
61
- const jobFilePath = path.join(jobDir, 'job.json');
62
- fs.writeFileSync(jobFilePath, JSON.stringify(jobSpec, null, 2) + '\n', 'utf8');
63
- return jobFilePath;
64
- }
65
-
66
- function runInlineJob(args) {
67
- if (args.runFile) {
68
- runner.runJobFromFile(args.runFile);
69
- return;
70
- }
71
-
72
- if (args.resumeJobId) {
73
- runner.resumeJob(args.resumeJobId);
74
- return;
75
- }
76
-
77
- throw new Error('Unsupported inline hosted job launch');
78
- }
79
-
80
- function launchRunner(args, options = {}) {
81
- const launchMode = options.launchMode || process.env.THUMBGATE_HOSTED_JOB_LAUNCH_MODE || BACKGROUND_LAUNCH_MODE;
82
- if (launchMode === INLINE_LAUNCH_MODE) {
83
- runInlineJob(args);
84
- return {
85
- launchMode,
86
- pid: process.pid,
87
- };
88
- }
89
-
90
- const runnerArgs = [];
91
- if (args.runFile) {
92
- runnerArgs.push(`--run-file=${args.runFile}`);
93
- } else if (args.resumeJobId) {
94
- runnerArgs.push(`--resume=${args.resumeJobId}`);
95
- } else {
96
- throw new Error('Hosted job launch requires runFile or resumeJobId');
97
- }
98
-
99
- const child = spawn(process.execPath, [RUNNER_SCRIPT_PATH, ...runnerArgs], {
100
- cwd: options.cwd || process.cwd(),
101
- env: process.env,
102
- detached: true,
103
- stdio: 'ignore',
104
- });
105
- child.unref();
106
- return {
107
- launchMode,
108
- pid: child.pid,
109
- };
110
- }
111
-
112
- function prepareManagedJob(jobSpec, options = {}) {
113
- const jobId = options.jobId || jobSpec.id || createHostedJobId(options.jobPrefix || 'job');
114
- const finalSpec = {
115
- ...jobSpec,
116
- id: jobId,
117
- };
118
- const jobFilePath = writeJobFile(jobId, finalSpec);
119
- const queuedState = runner.queueJob({
120
- ...finalSpec,
121
- jobFilePath,
122
- });
123
- return {
124
- jobId,
125
- jobFilePath,
126
- state: queuedState,
127
- jobSpec: {
128
- ...finalSpec,
129
- jobFilePath,
130
- },
131
- };
132
- }
133
-
134
- function launchManagedJob(jobSpec, options = {}) {
135
- const prepared = prepareManagedJob(jobSpec, options);
136
- const launch = launchRunner({ runFile: prepared.jobFilePath }, options);
137
- return {
138
- jobId: prepared.jobId,
139
- jobFilePath: prepared.jobFilePath,
140
- launchMode: launch.launchMode,
141
- pid: launch.pid || null,
142
- state: runner.readJobState(prepared.jobId) || prepared.state,
143
- };
144
- }
145
-
146
- function buildManagedDpoExportJob(params = {}) {
147
- const command = [
148
- shellQuote(process.execPath),
149
- shellQuote(MANAGED_DPO_EXPORT_SCRIPT_PATH),
150
- ];
151
-
152
- if (params.inputPath) {
153
- command.push('--inputPath', shellQuote(params.inputPath));
154
- } else if (params.memoryLogPath) {
155
- command.push('--memoryLogPath', shellQuote(params.memoryLogPath));
156
- }
157
-
158
- if (params.outputPath) {
159
- command.push('--outputPath', shellQuote(params.outputPath));
160
- }
161
-
162
- return {
163
- tags: ['hosted-job', 'dpo-export'],
164
- skill: 'hosted-dpo-export',
165
- autoImprove: false,
166
- verificationMode: 'none',
167
- recordFeedback: false,
168
- stages: [
169
- {
170
- name: 'export_dpo_pairs',
171
- command: command.join(' '),
172
- },
173
- ],
174
- };
175
- }
176
-
177
- function launchDpoExportJob(params = {}, options = {}) {
178
- return launchManagedJob(buildManagedDpoExportJob(params), {
179
- ...options,
180
- jobPrefix: 'dpo_export',
181
- });
182
- }
183
-
184
- function launchHarnessJob(identifier, inputs = {}, options = {}) {
185
- const jobId = options.jobId || createHostedJobId('harness');
186
- const jobSpec = buildHarnessJob(identifier, inputs, {
187
- jobId,
188
- skill: options.skill,
189
- partnerProfile: options.partnerProfile,
190
- autoImprove: options.autoImprove,
191
- });
192
- return launchManagedJob(jobSpec, {
193
- ...options,
194
- jobId,
195
- jobPrefix: 'harness',
196
- });
197
- }
198
-
199
- function resumeHostedJob(jobId, options = {}) {
200
- const state = runner.readJobState(jobId);
201
- if (!state) {
202
- const error = new Error(`No persisted state found for job ${jobId}`);
203
- error.statusCode = 404;
204
- throw error;
205
- }
206
-
207
- if (['completed', 'failed', 'cancelled'].includes(state.status)) {
208
- const error = new Error(`Job ${jobId} is already ${state.status}`);
209
- error.statusCode = 409;
210
- throw error;
211
- }
212
-
213
- const launch = launchRunner({ resumeJobId: jobId }, options);
214
- return {
215
- jobId,
216
- launchMode: launch.launchMode,
217
- pid: launch.pid || null,
218
- state: runner.readJobState(jobId) || state,
219
- };
220
- }
221
-
222
- function pauseQueuedJob(jobId, metadata = {}) {
223
- runner.clearJobControl(jobId);
224
- return updateIdleJobState(jobId, (state) => ({
225
- ...state,
226
- status: 'paused',
227
- updatedAt: nowIso(),
228
- pausedAt: nowIso(),
229
- stopReason: metadata && metadata.reason ? metadata.reason : 'pause_requested',
230
- }));
231
- }
232
-
233
- function cancelQueuedJob(jobId, metadata = {}) {
234
- runner.clearJobControl(jobId);
235
- return updateIdleJobState(jobId, (state) => ({
236
- ...state,
237
- status: 'cancelled',
238
- updatedAt: nowIso(),
239
- endedAt: nowIso(),
240
- stopReason: metadata && metadata.reason ? metadata.reason : 'cancel_requested',
241
- }));
242
- }
243
-
244
- module.exports = {
245
- BACKGROUND_LAUNCH_MODE,
246
- INLINE_LAUNCH_MODE,
247
- buildManagedDpoExportJob,
248
- cancelQueuedJob,
249
- createHostedJobId,
250
- launchDpoExportJob,
251
- launchHarnessJob,
252
- launchManagedJob,
253
- pauseQueuedJob,
254
- prepareManagedJob,
255
- resumeHostedJob,
256
- };