@detiq/crawler 1.1.26 → 1.1.27
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/dist/crawler.js +2 -3
- package/dist/crawler.js.map +2 -2
- package/dist/job-queue.js +299 -64
- package/dist/job-queue.js.map +3 -3
- package/package.json +1 -1
- package/src/crawler.ts +5 -2
- package/src/failure-analysis-agent.ts +2 -1
- package/src/job-queue.ts +109 -30
- package/types/crawler.d.ts +4 -0
- package/types/job-queue.d.ts +44 -3
package/package.json
CHANGED
package/src/crawler.ts
CHANGED
|
@@ -373,7 +373,6 @@ async function launchChromeCDP(
|
|
|
373
373
|
'--remote-allow-origins=*',
|
|
374
374
|
'--no-first-run',
|
|
375
375
|
'--no-default-browser-check',
|
|
376
|
-
'--window-size=1280,900',
|
|
377
376
|
],
|
|
378
377
|
});
|
|
379
378
|
|
|
@@ -634,6 +633,10 @@ export async function crawlProject(opts: {
|
|
|
634
633
|
/** When false, disables the puppeteer-extra stealth plugin (playwright-extra) for this crawl.
|
|
635
634
|
* Default true — stealth is applied unless explicitly set to false. */
|
|
636
635
|
stealth?: boolean;
|
|
636
|
+
/** Desktop viewport width in pixels (default: 1440). */
|
|
637
|
+
viewportWidth?: number;
|
|
638
|
+
/** Desktop viewport height in pixels (default: 900). */
|
|
639
|
+
viewportHeight?: number;
|
|
637
640
|
/** Called every 10 successful screen captures with current visited URLs and remaining queue — used for checkpoint persistence. */
|
|
638
641
|
onCheckpoint?: (visitedUrls: string[], pendingQueue: string[]) => Promise<void>;
|
|
639
642
|
/** Discovery mode only: called after each page with the current set of discovered original URLs — enables streaming partial results to callers. */
|
|
@@ -829,7 +832,7 @@ export async function crawlProject(opts: {
|
|
|
829
832
|
'--disable-blink-features=AutomationControlled',
|
|
830
833
|
'--disable-http2',
|
|
831
834
|
'--lang=en-US,en',
|
|
832
|
-
|
|
835
|
+
`--window-size=${opts.viewportWidth ?? 1440},${opts.viewportHeight ?? 900}`,
|
|
833
836
|
'--disable-features=IsolateOrigins,site-per-process',
|
|
834
837
|
];
|
|
835
838
|
|
|
@@ -36,7 +36,8 @@ Error: ${ctx.error}${consoleSection}${networkSection}`;
|
|
|
36
36
|
if (ctx.screenshotPath) {
|
|
37
37
|
try {
|
|
38
38
|
const img = readFileSync(ctx.screenshotPath);
|
|
39
|
-
const
|
|
39
|
+
const imgSeed = deterministicSeed(`failure-img:${ctx.tenantId ?? ''}:${(ctx.stepDescription ?? '').slice(0, 100)}:${ctx.error.slice(0, 200)}`);
|
|
40
|
+
const result = await askTrackedWithImages('', userText, [{ base64: img.toString('base64'), mediaType: 'image/png' }], llmCfg, { temperature: 0, seed: imgSeed });
|
|
40
41
|
return result.text || null;
|
|
41
42
|
} catch { /* fall through to text-only */ }
|
|
42
43
|
}
|
package/src/job-queue.ts
CHANGED
|
@@ -18,40 +18,18 @@ import crypto from 'node:crypto';
|
|
|
18
18
|
import { withTenant, prismaAdmin } from '@detiq/database';
|
|
19
19
|
import { crawlJobsTotal, crawlJobDurationSeconds } from '@detiq/core';
|
|
20
20
|
import { logger } from './logger.js';
|
|
21
|
+
import { AppBrain, getProjectStorageConfig, buildArtifactKey, uploadArtifact, getTenantStorageConfig, uploadScreenshotToTenantStorage, uploadFileToTenantStorage, notifyRunCompletion, recordLLMCall, downloadBufferFromTenantStorage, dispatchToTenantAdmins } from '@detiq/app-brain';
|
|
21
22
|
|
|
22
23
|
async function notifyAdmins(tenantId: string, data: {
|
|
23
24
|
type: string; title: string; body: string; metadata?: Record<string, unknown>
|
|
24
25
|
}) {
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
-
// prisma call with no app.current_tenant set gets silently filtered to
|
|
28
|
-
// zero rows (SELECT) or rejected (INSERT), not an error, so this call
|
|
29
|
-
// site had been a complete no-op with no visible failure until fixed.
|
|
30
|
-
const members = await withTenant(tenantId, (tx) =>
|
|
31
|
-
tx.membership.findMany({
|
|
32
|
-
where: { tenantId, role: { in: ['OWNER', 'ADMIN'] } },
|
|
33
|
-
select: { userId: true },
|
|
34
|
-
})
|
|
35
|
-
) as any[];
|
|
36
|
-
await withTenant(tenantId, (tx) =>
|
|
37
|
-
tx.notification.createMany({
|
|
38
|
-
data: members.map((m: { userId: string }) => ({
|
|
39
|
-
id: `notif_${crypto.randomUUID()}`,
|
|
40
|
-
tenantId,
|
|
41
|
-
userId: m.userId,
|
|
42
|
-
type: data.type,
|
|
43
|
-
title: data.title,
|
|
44
|
-
body: data.body,
|
|
45
|
-
metadata: data.metadata as any,
|
|
46
|
-
})),
|
|
47
|
-
})
|
|
48
|
-
);
|
|
27
|
+
await dispatchToTenantAdmins(tenantId, data as any);
|
|
49
28
|
} catch (e) {
|
|
50
29
|
console.error('[notify] failed:', e);
|
|
51
30
|
}
|
|
52
31
|
}
|
|
53
|
-
import {
|
|
54
|
-
import { generateTaxonomySkillFiles, groupScreensWithAI, generateExecutionPlan, refreshTestCasesSkillFile, refreshTestDataSkillFile, refreshTestScriptSkillFile, refreshRequirementsCoverageSkillFile, refreshTestExecutionHistorySkillFile, refreshVisualRegressionHistorySkillFile, refreshLocatorStrategyWithHealingData, refreshFieldCatalogSkillFile, detectFieldsFromHtml, mergeIntoCatalog } from '@detiq/agents';
|
|
32
|
+
import { generateTaxonomySkillFiles, groupScreensWithAI, generateExecutionPlan, refreshTestCasesSkillFile, refreshTestDataSkillFile, refreshTestScriptSkillFile, refreshRequirementsCoverageSkillFile, refreshTestExecutionHistorySkillFile, refreshVisualRegressionHistorySkillFile, refreshLocatorStrategyWithHealingData, refreshFieldCatalogSkillFile, detectFieldsFromHtml, mergeIntoCatalog, generateArtifactIntelligence } from '@detiq/agents';
|
|
55
33
|
import type { ExecutionStep } from '@detiq/agents';
|
|
56
34
|
import { crawlProject, domFallbackElements } from './crawler.js';
|
|
57
35
|
import { httpCrawl } from './http-engine.js';
|
|
@@ -1688,7 +1666,7 @@ Each test case must reference its reqRef (the requirement ID like REQ-001).`;
|
|
|
1688
1666
|
|
|
1689
1667
|
let parsed: { cases: any[] } = { cases: [] };
|
|
1690
1668
|
try {
|
|
1691
|
-
const reqSeed = deterministicSeed(
|
|
1669
|
+
const reqSeed = deterministicSeed(`req-cases:${reqs.map((r: any) => r.reqId).sort().join('|').slice(0, 300)}`);
|
|
1692
1670
|
const { text, usage } = await askTracked(system, user, llmCfg, undefined, undefined, { temperature: 0, seed: reqSeed });
|
|
1693
1671
|
if (usage) recordLLMCall({ tenantId, projectId, jobId, agentType: 'generate_tests_from_reqs', label: 'test_case_generation', usage }).catch(() => { });
|
|
1694
1672
|
const json = text.match(/\{[\s\S]*\}/)?.[0];
|
|
@@ -2561,8 +2539,13 @@ async function processGenerateScreenTestsJob(jobId: string, config: any) {
|
|
|
2561
2539
|
const crawlCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => null);
|
|
2562
2540
|
const deviceProfiles: string[] = (crawlCfg as any)?.deviceProfiles ?? ['DESKTOP'];
|
|
2563
2541
|
|
|
2542
|
+
const existingAiCases: number = await withTenant(tenantId, (tx) =>
|
|
2543
|
+
(tx as any).testCase.count({ where: { tenantId, screenId, source: 'AI_GENERATED' } })
|
|
2544
|
+
);
|
|
2545
|
+
const generationMode = existingAiCases > 0 ? 'replace' : undefined;
|
|
2546
|
+
|
|
2564
2547
|
for (const viewport of deviceProfiles) {
|
|
2565
|
-
await DualContextOrchestrator.execute({ tenantId, projectId, targetScreenId: screenId, jobId, viewport });
|
|
2548
|
+
await DualContextOrchestrator.execute({ tenantId, projectId, targetScreenId: screenId, jobId, viewport, ...(generationMode ? { mode: generationMode } : {}) });
|
|
2566
2549
|
}
|
|
2567
2550
|
|
|
2568
2551
|
await updateJobProgress(tenantId, jobId, 80, 'Test cases generated');
|
|
@@ -3197,13 +3180,111 @@ async function processGenerateScriptJob(jobId: string, config: any): Promise<voi
|
|
|
3197
3180
|
}
|
|
3198
3181
|
}
|
|
3199
3182
|
|
|
3183
|
+
function queueImportedArtifactSkillRefresh(tenantId: string, projectId: string, reason: string): void {
|
|
3184
|
+
setImmediate(() => {
|
|
3185
|
+
import('@detiq/agents')
|
|
3186
|
+
.then((agentsModule) => {
|
|
3187
|
+
const agents = agentsModule as any;
|
|
3188
|
+
return Promise.allSettled([
|
|
3189
|
+
agents.refreshArtifactKnowledgeSkillFile(tenantId, projectId),
|
|
3190
|
+
agents.refreshRequirementsCoverageSkillFile(tenantId, projectId),
|
|
3191
|
+
]);
|
|
3192
|
+
})
|
|
3193
|
+
.then((results) => {
|
|
3194
|
+
results?.forEach((result) => {
|
|
3195
|
+
if (result.status === 'rejected') {
|
|
3196
|
+
console.warn(`[artifacts] skill refresh failed after ${reason}:`, result.reason?.message ?? result.reason);
|
|
3197
|
+
}
|
|
3198
|
+
});
|
|
3199
|
+
})
|
|
3200
|
+
.catch((e) => console.warn(`[artifacts] skill refresh failed after ${reason}:`, e?.message ?? e));
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3200
3204
|
// Registers the type→processor dispatch table used by the durable worker (both the
|
|
3201
3205
|
// enqueueJob() fast-path kick and startJobWorkerLoop()'s poll). Declared at module
|
|
3202
3206
|
// bottom so it can reference every process*Job function by name; function
|
|
3203
3207
|
// declarations are hoisted, so this could technically live anywhere in the file,
|
|
3204
3208
|
// but keeping it here alongside the last definition keeps the mapping easy to audit
|
|
3205
3209
|
// against the actual functions above.
|
|
3210
|
+
const CONTENT_DETECTED_DOC_TYPES = new Set(['FUNCTIONIZE_TC', 'SELENIUM_SCRIPT', 'ENV_CONFIG']);
|
|
3211
|
+
const AI_DOC_TYPES = new Set([
|
|
3212
|
+
'REQUIREMENTS', 'USER_STORY', 'TEST_CASES', 'TEST_DATA', 'AUTOMATION_SCRIPT', 'API_SPEC', 'ARCHITECTURE', 'REPORT', 'OTHER',
|
|
3213
|
+
'SPREADSHEET', 'DATASHEET',
|
|
3214
|
+
]);
|
|
3215
|
+
const AI_DOC_TYPE_ALIASES: Record<string, string> = {
|
|
3216
|
+
REQUIREMENT: 'REQUIREMENTS', SPEC: 'REQUIREMENTS', SPECIFICATION: 'REQUIREMENTS',
|
|
3217
|
+
USER_STORIES: 'USER_STORY', STORY: 'USER_STORY',
|
|
3218
|
+
TEST_CASE: 'TEST_CASES', TESTCASES: 'TEST_CASES',
|
|
3219
|
+
TESTDATA: 'TEST_DATA', SCRIPT: 'AUTOMATION_SCRIPT', AUTOMATION: 'AUTOMATION_SCRIPT',
|
|
3220
|
+
};
|
|
3221
|
+
function normalizeAiDocType(raw: unknown): string {
|
|
3222
|
+
const key = String(raw ?? '').trim().toUpperCase().replace(/[\s-]+/g, '_');
|
|
3223
|
+
const mapped = AI_DOC_TYPE_ALIASES[key] ?? key;
|
|
3224
|
+
return AI_DOC_TYPES.has(mapped) ? mapped : 'OTHER';
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
async function processArtifactIntelJob(jobId: string, config: any) {
|
|
3228
|
+
const { tenantId, projectId, artifactId, artifactName, isReprocess } = config;
|
|
3229
|
+
try {
|
|
3230
|
+
await updateJobProgress(tenantId, jobId, 10, 'Downloading artifact content');
|
|
3231
|
+
|
|
3232
|
+
const artifact = await withTenant(tenantId, (tx) =>
|
|
3233
|
+
(tx as any).projectArtifact.findUnique({ where: { id: artifactId }, select: { storageKey: true, docType: true } })
|
|
3234
|
+
) as any;
|
|
3235
|
+
|
|
3236
|
+
if (!artifact || !artifact.storageKey) {
|
|
3237
|
+
await updateJobProgress(tenantId, jobId, 100, 'Artifact deleted or missing content, skipping.');
|
|
3238
|
+
await withTenant(tenantId, (tx) =>
|
|
3239
|
+
(tx as any).platformJob.update({ where: { id: jobId }, data: { status: 'DONE', finishedAt: new Date() } })
|
|
3240
|
+
);
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
const storageCfg = await getTenantStorageConfig(tenantId);
|
|
3245
|
+
if (!storageCfg) {
|
|
3246
|
+
throw new Error('Tenant storage configuration missing.');
|
|
3247
|
+
}
|
|
3248
|
+
const contentBuffer = await downloadBufferFromTenantStorage(storageCfg, artifact.storageKey);
|
|
3249
|
+
const content = contentBuffer.toString('utf8');
|
|
3250
|
+
|
|
3251
|
+
await updateJobProgress(tenantId, jobId, 40, 'Generating intelligence');
|
|
3252
|
+
|
|
3253
|
+
const intel = await generateArtifactIntelligence(tenantId, projectId, artifactId, content, artifactName, jobId);
|
|
3254
|
+
|
|
3255
|
+
const finalDocType = artifact.docType && CONTENT_DETECTED_DOC_TYPES.has(artifact.docType)
|
|
3256
|
+
? artifact.docType
|
|
3257
|
+
: normalizeAiDocType(intel.docType);
|
|
3258
|
+
|
|
3259
|
+
if (finalDocType !== intel.docType) {
|
|
3260
|
+
await withTenant(tenantId, (tx) =>
|
|
3261
|
+
(tx as any).projectArtifact.update({ where: { id: artifactId }, data: { docType: finalDocType } })
|
|
3262
|
+
).catch((err: any) => console.warn(`[artifact-intel] docType fix-up failed for "${artifactName}":`, err?.message));
|
|
3263
|
+
intel.docType = finalDocType;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
await updateJobProgress(tenantId, jobId, 90, 'Saving results');
|
|
3267
|
+
|
|
3268
|
+
await withTenant(tenantId, (tx) =>
|
|
3269
|
+
(tx as any).platformJob.update({ where: { id: jobId }, data: { result: { artifactId, artifactName, summary: intel.summary, keywords: intel.keywords, docType: intel.docType }, status: 'DONE', finishedAt: new Date(), progress: 100 } })
|
|
3270
|
+
);
|
|
3271
|
+
|
|
3272
|
+
queueImportedArtifactSkillRefresh(tenantId, projectId, isReprocess ? 'artifact-reprocess' : 'artifact-intel');
|
|
3273
|
+
} catch (err: any) {
|
|
3274
|
+
const msg = String(err?.message ?? err);
|
|
3275
|
+
logger.error({ jobId, err: msg }, '[artifact-intel] failed');
|
|
3276
|
+
await withTenant(tenantId, (tx) =>
|
|
3277
|
+
(tx as any).projectArtifact.update({ where: { id: artifactId }, data: { status: 'ERROR', summary: 'AI analysis failed' } })
|
|
3278
|
+
).catch(() => { });
|
|
3279
|
+
await withTenant(tenantId, (tx) =>
|
|
3280
|
+
(tx as any).platformJob.update({ where: { id: jobId }, data: { status: 'FAILED', finishedAt: new Date(), error: msg.slice(0, 1000) } })
|
|
3281
|
+
).catch(() => { });
|
|
3282
|
+
queueImportedArtifactSkillRefresh(tenantId, projectId, 'artifact-intel-failed');
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3206
3286
|
jobDispatchTable = {
|
|
3287
|
+
ARTIFACT_INTEL: processArtifactIntelJob,
|
|
3207
3288
|
CRAWL: processJob,
|
|
3208
3289
|
CAPTURE_STEPS: processCaptureStepsJob,
|
|
3209
3290
|
DISCOVER: processDiscoverJob,
|
|
@@ -3726,8 +3807,6 @@ async function processBatchGenerateAlignedJob(jobId: string, config: any) {
|
|
|
3726
3807
|
) as any;
|
|
3727
3808
|
if (!conn) throw new Error(`RepoConnection ${repoConnectionId} not found`);
|
|
3728
3809
|
|
|
3729
|
-
const { decrypt } = await import('@detiq/core');
|
|
3730
|
-
|
|
3731
3810
|
const {
|
|
3732
3811
|
generateScriptAligned, deriveRepoTargetPath, validateGeneratedScript,
|
|
3733
3812
|
mapWithConcurrency, SCRIPT_GEN_CONCURRENCY,
|
|
@@ -3992,7 +4071,7 @@ function buildPRBody(scripts: any[], projectId: string): string {
|
|
|
3992
4071
|
// ── NEW_PAGES_PIPELINE — post-crawl: detect new screens, generate tests + scripts ──
|
|
3993
4072
|
|
|
3994
4073
|
async function processNewPagesPipelineJob(jobId: string, config: any) {
|
|
3995
|
-
const { tenantId, projectId, crawlJobId, repoConnectionId, autoPR = false } = config;
|
|
4074
|
+
const { tenantId, projectId, crawlJobId: _crawlJobId, repoConnectionId, autoPR = false } = config;
|
|
3996
4075
|
try {
|
|
3997
4076
|
await updateJobProgress(tenantId, jobId, 5, 'Detecting new screens');
|
|
3998
4077
|
|
package/types/crawler.d.ts
CHANGED
|
@@ -167,6 +167,10 @@ export declare function crawlProject(opts: {
|
|
|
167
167
|
/** When false, disables the puppeteer-extra stealth plugin (playwright-extra) for this crawl.
|
|
168
168
|
* Default true — stealth is applied unless explicitly set to false. */
|
|
169
169
|
stealth?: boolean;
|
|
170
|
+
/** Desktop viewport width in pixels (default: 1440). */
|
|
171
|
+
viewportWidth?: number;
|
|
172
|
+
/** Desktop viewport height in pixels (default: 900). */
|
|
173
|
+
viewportHeight?: number;
|
|
170
174
|
/** Called every 10 successful screen captures with current visited URLs and remaining queue — used for checkpoint persistence. */
|
|
171
175
|
onCheckpoint?: (visitedUrls: string[], pendingQueue: string[]) => Promise<void>;
|
|
172
176
|
/** Discovery mode only: called after each page with the current set of discovered original URLs — enables streaming partial results to callers. */
|
package/types/job-queue.d.ts
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
* (JOB_QUEUE_BACKEND=sqs, see sqs-queue.ts) for horizontally-scaled crawler
|
|
14
14
|
* fleets; every other job type always goes through the DB-backed queue.
|
|
15
15
|
*/
|
|
16
|
+
export declare enum FailureType {
|
|
17
|
+
TRANSIENT = "TRANSIENT",// network timeout, rate limit, 429, 503
|
|
18
|
+
INFRASTRUCTURE = "INFRASTRUCTURE",// DB connection, OOM, worker crash
|
|
19
|
+
LOGICAL = "LOGICAL",// bad input, business rule violation, 400/422
|
|
20
|
+
FATAL = "FATAL"
|
|
21
|
+
}
|
|
22
|
+
export declare function classifyFailure(error: string | Error | unknown): FailureType;
|
|
16
23
|
export declare function generateTOTP(secret: string): string;
|
|
17
24
|
export interface PlatformJobConfig {
|
|
18
25
|
tenantId: string;
|
|
@@ -114,6 +121,7 @@ export interface PlatformJobConfig {
|
|
|
114
121
|
/** Set on role-crawl sub-jobs — profile name tagged on discovered screens via roleAccess column. */
|
|
115
122
|
roleProfileName?: string;
|
|
116
123
|
}
|
|
124
|
+
export declare function getActiveExecutionCount(): number;
|
|
117
125
|
type ClaimedJob = {
|
|
118
126
|
id: string;
|
|
119
127
|
tenantId: string;
|
|
@@ -144,7 +152,9 @@ export declare function requeueOrphanedJobs(): Promise<void>;
|
|
|
144
152
|
/** Dispatches a claimed job to its type's processor. Last-resort safety net — every
|
|
145
153
|
* process*Job function already catches and records its own failures. */
|
|
146
154
|
export declare function dispatchClaimedJob(job: ClaimedJob): Promise<void>;
|
|
147
|
-
/** Re-promotes FAILED jobs to
|
|
155
|
+
/** Re-promotes FAILED jobs to RETRY_WAIT if they are eligible for retry based on per-type limits.
|
|
156
|
+
* RETRY_WAIT makes the waiting period visible in the UI. A separate sweep then promotes
|
|
157
|
+
* RETRY_WAIT → QUEUED once nextRetryAt <= NOW(). */
|
|
148
158
|
export declare function retryEligibleFailedJobs(): Promise<void>;
|
|
149
159
|
/** Promotes FAILED jobs to DLQ if they exceed their per-type retry limits. */
|
|
150
160
|
export declare function promoteToDeadLetterQueue(): Promise<void>;
|
|
@@ -169,7 +179,7 @@ export declare function createTrackedJob(type: string, config: {
|
|
|
169
179
|
[k: string]: any;
|
|
170
180
|
}): Promise<string>;
|
|
171
181
|
/** Mark a tracked job DONE or FAILED. Safe to call in finally — swallows errors.
|
|
172
|
-
*
|
|
182
|
+
* GAP-14/15: Uses failure-type classification for per-type backoff on FAILED. */
|
|
173
183
|
export declare function finishTrackedJob(tenantId: string, jobId: string, error?: string): Promise<void>;
|
|
174
184
|
/** Enqueue a generic platform job — returns the job ID immediately. */
|
|
175
185
|
export declare function enqueueJob(type: string, config: any): Promise<string>;
|
|
@@ -184,8 +194,10 @@ export declare function getJob(tenantId: string, jobId: string): Promise<{
|
|
|
184
194
|
status: string;
|
|
185
195
|
tenantId: string;
|
|
186
196
|
projectId: string;
|
|
197
|
+
inline: boolean;
|
|
187
198
|
config: import("@prisma/client/runtime/library").JsonValue;
|
|
188
199
|
retryCount: number;
|
|
200
|
+
failureType: string | null;
|
|
189
201
|
queuedAt: Date;
|
|
190
202
|
startedAt: Date | null;
|
|
191
203
|
finishedAt: Date | null;
|
|
@@ -199,6 +211,18 @@ export declare function getJob(tenantId: string, jobId: string): Promise<{
|
|
|
199
211
|
nextRetryAt: Date | null;
|
|
200
212
|
dlq: boolean;
|
|
201
213
|
dlqReason: string | null;
|
|
214
|
+
leaseExpiresAt: Date | null;
|
|
215
|
+
fencingToken: string | null;
|
|
216
|
+
checkpoint: import("@prisma/client/runtime/library").JsonValue | null;
|
|
217
|
+
checkpointAt: Date | null;
|
|
218
|
+
parentJobId: string | null;
|
|
219
|
+
rootJobId: string | null;
|
|
220
|
+
currentStep: string | null;
|
|
221
|
+
requeuedAt: Date | null;
|
|
222
|
+
dlqAt: Date | null;
|
|
223
|
+
dlqRedriveCount: number;
|
|
224
|
+
submittedBy: string | null;
|
|
225
|
+
priority: number;
|
|
202
226
|
} | null>;
|
|
203
227
|
/** List recent jobs for a project. */
|
|
204
228
|
export declare function listJobs(tenantId: string, projectId: string): Promise<{
|
|
@@ -211,8 +235,10 @@ export declare function listJobs(tenantId: string, projectId: string): Promise<{
|
|
|
211
235
|
status: string;
|
|
212
236
|
tenantId: string;
|
|
213
237
|
projectId: string;
|
|
238
|
+
inline: boolean;
|
|
214
239
|
config: import("@prisma/client/runtime/library").JsonValue;
|
|
215
240
|
retryCount: number;
|
|
241
|
+
failureType: string | null;
|
|
216
242
|
queuedAt: Date;
|
|
217
243
|
startedAt: Date | null;
|
|
218
244
|
finishedAt: Date | null;
|
|
@@ -226,8 +252,23 @@ export declare function listJobs(tenantId: string, projectId: string): Promise<{
|
|
|
226
252
|
nextRetryAt: Date | null;
|
|
227
253
|
dlq: boolean;
|
|
228
254
|
dlqReason: string | null;
|
|
255
|
+
leaseExpiresAt: Date | null;
|
|
256
|
+
fencingToken: string | null;
|
|
257
|
+
checkpoint: import("@prisma/client/runtime/library").JsonValue | null;
|
|
258
|
+
checkpointAt: Date | null;
|
|
259
|
+
parentJobId: string | null;
|
|
260
|
+
rootJobId: string | null;
|
|
261
|
+
currentStep: string | null;
|
|
262
|
+
requeuedAt: Date | null;
|
|
263
|
+
dlqAt: Date | null;
|
|
264
|
+
dlqRedriveCount: number;
|
|
265
|
+
submittedBy: string | null;
|
|
266
|
+
priority: number;
|
|
229
267
|
}[]>;
|
|
230
|
-
/** Cancel a RUNNING or QUEUED job.
|
|
268
|
+
/** Cancel a RUNNING or QUEUED job.
|
|
269
|
+
* Sets status to CANCEL_REQUESTED (GAP-08) so the worker's abort handler can
|
|
270
|
+
* detect the request and set CANCELLED + finishedAt after the underlying work stops.
|
|
271
|
+
* The in-process AbortController is also triggered for same-process jobs. */
|
|
231
272
|
export declare function cancelJob(tenantId: string, jobId: string): Promise<void>;
|
|
232
273
|
/** Re-enqueue a job using the same config as a previous job. */
|
|
233
274
|
export declare function rerunJob(tenantId: string, jobId: string): Promise<string>;
|