hereya-cli 0.101.0 → 0.102.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/README.md +117 -66
- package/dist/backend/cloud/cloud-backend/executor-jobs.d.ts +39 -0
- package/dist/backend/cloud/cloud-backend/executor-jobs.js +58 -0
- package/dist/backend/cloud/cloud-backend.d.ts +39 -0
- package/dist/backend/cloud/cloud-backend.js +9 -0
- package/dist/commands/job/cancel/index.d.ts +12 -0
- package/dist/commands/job/cancel/index.js +31 -0
- package/dist/commands/job/list/index.d.ts +11 -0
- package/dist/commands/job/list/index.js +54 -0
- package/dist/lib/remote-job-utils.js +50 -1
- package/oclif.manifest.json +267 -167
- package/package.json +4 -1
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { CloudHttpConfig } from './utils.js';
|
|
2
|
+
export declare function cancelExecutorJob(config: CloudHttpConfig, input: {
|
|
3
|
+
jobId: string;
|
|
4
|
+
workspace: string;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
reason: string;
|
|
7
|
+
status?: string;
|
|
8
|
+
success: false;
|
|
9
|
+
} | {
|
|
10
|
+
status: string;
|
|
11
|
+
success: true;
|
|
12
|
+
}>;
|
|
2
13
|
export declare function generateExecutorToken(config: CloudHttpConfig, input: {
|
|
3
14
|
workspace: string;
|
|
4
15
|
}): Promise<{
|
|
@@ -27,6 +38,25 @@ export declare function getExecutorJobStatus(config: CloudHttpConfig, input: {
|
|
|
27
38
|
reason: string;
|
|
28
39
|
success: false;
|
|
29
40
|
}>;
|
|
41
|
+
export declare function listExecutorJobs(config: CloudHttpConfig, input: {
|
|
42
|
+
limit?: number;
|
|
43
|
+
status?: string;
|
|
44
|
+
workspace: string;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
jobs: Array<{
|
|
47
|
+
attempts: number;
|
|
48
|
+
claimedBy: null | string;
|
|
49
|
+
createdAt: string;
|
|
50
|
+
id: string;
|
|
51
|
+
status: string;
|
|
52
|
+
type: string;
|
|
53
|
+
updatedAt: string;
|
|
54
|
+
}>;
|
|
55
|
+
success: true;
|
|
56
|
+
} | {
|
|
57
|
+
reason: string;
|
|
58
|
+
success: false;
|
|
59
|
+
}>;
|
|
30
60
|
export declare function pollExecutorJobs(config: CloudHttpConfig, input: {
|
|
31
61
|
executorId?: string;
|
|
32
62
|
workspace: string;
|
|
@@ -58,6 +88,15 @@ export declare function revokeExecutorToken(config: CloudHttpConfig, input: {
|
|
|
58
88
|
} | {
|
|
59
89
|
success: true;
|
|
60
90
|
}>;
|
|
91
|
+
export declare function rewakeExecutorJob(config: CloudHttpConfig, input: {
|
|
92
|
+
jobId: string;
|
|
93
|
+
workspace: string;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
reason?: string;
|
|
96
|
+
rewoken?: boolean;
|
|
97
|
+
status?: string;
|
|
98
|
+
success: boolean;
|
|
99
|
+
}>;
|
|
61
100
|
export declare function submitExecutorJob(config: CloudHttpConfig, input: {
|
|
62
101
|
payload: object;
|
|
63
102
|
type: 'app-deploy' | 'app-destroy' | 'deploy' | 'destroy' | 'provision' | 'resolve-env' | 'undeploy';
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { safeResponseJson } from './utils.js';
|
|
2
|
+
export async function cancelExecutorJob(config, input) {
|
|
3
|
+
const response = await fetch(`${config.url}/api/workspaces/${encodeURIComponent(input.workspace)}/jobs/${encodeURIComponent(input.jobId)}/cancel`, {
|
|
4
|
+
headers: { 'Authorization': `Bearer ${config.accessToken}` },
|
|
5
|
+
method: 'POST',
|
|
6
|
+
});
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
const error = await safeResponseJson(response);
|
|
9
|
+
return {
|
|
10
|
+
reason: error.error || `Failed to cancel job (status ${response.status})`,
|
|
11
|
+
status: error.status,
|
|
12
|
+
success: false,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const result = await response.json();
|
|
16
|
+
return { status: result.status, success: true };
|
|
17
|
+
}
|
|
2
18
|
export async function generateExecutorToken(config, input) {
|
|
3
19
|
const response = await fetch(`${config.url}/api/workspaces/${encodeURIComponent(input.workspace)}/executor-token`, {
|
|
4
20
|
headers: { 'Authorization': `Bearer ${config.accessToken}` },
|
|
@@ -33,6 +49,25 @@ export async function getExecutorJobStatus(config, input) {
|
|
|
33
49
|
const result = await response.json();
|
|
34
50
|
return { job: result.job, success: true };
|
|
35
51
|
}
|
|
52
|
+
export async function listExecutorJobs(config, input) {
|
|
53
|
+
const url = new URL(`${config.url}/api/workspaces/${encodeURIComponent(input.workspace)}/jobs`);
|
|
54
|
+
if (input.limit !== undefined) {
|
|
55
|
+
url.searchParams.set('limit', String(input.limit));
|
|
56
|
+
}
|
|
57
|
+
if (input.status) {
|
|
58
|
+
url.searchParams.set('status', input.status);
|
|
59
|
+
}
|
|
60
|
+
const response = await fetch(url.toString(), {
|
|
61
|
+
headers: { 'Authorization': `Bearer ${config.accessToken}` },
|
|
62
|
+
method: 'GET',
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const error = await safeResponseJson(response);
|
|
66
|
+
return { reason: error.error || 'Failed to list jobs', success: false };
|
|
67
|
+
}
|
|
68
|
+
const result = await response.json();
|
|
69
|
+
return { jobs: result.jobs, success: true };
|
|
70
|
+
}
|
|
36
71
|
export async function pollExecutorJobs(config, input) {
|
|
37
72
|
const url = new URL(`${config.url}/api/executor/jobs`);
|
|
38
73
|
url.searchParams.set('workspace', input.workspace);
|
|
@@ -75,6 +110,29 @@ export async function revokeExecutorToken(config, input) {
|
|
|
75
110
|
}
|
|
76
111
|
return { success: true };
|
|
77
112
|
}
|
|
113
|
+
export async function rewakeExecutorJob(config, input) {
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${config.url}/api/workspaces/${encodeURIComponent(input.workspace)}/jobs/${encodeURIComponent(input.jobId)}/rewake`, {
|
|
116
|
+
body: JSON.stringify({}),
|
|
117
|
+
headers: { 'Authorization': `Bearer ${config.accessToken}`, 'Content-Type': 'application/json' },
|
|
118
|
+
method: 'POST',
|
|
119
|
+
});
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const error = await safeResponseJson(response);
|
|
122
|
+
return { reason: error.error || `Rewake failed with status ${response.status}`, success: false };
|
|
123
|
+
}
|
|
124
|
+
const result = await safeResponseJson(response);
|
|
125
|
+
return {
|
|
126
|
+
reason: result.reason,
|
|
127
|
+
rewoken: result.rewoken,
|
|
128
|
+
status: result.status,
|
|
129
|
+
success: Boolean(result.success),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return { reason: error instanceof Error ? error.message : String(error), success: false };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
78
136
|
export async function submitExecutorJob(config, input) {
|
|
79
137
|
const formData = new FormData();
|
|
80
138
|
formData.append('type', input.type);
|
|
@@ -27,6 +27,17 @@ export declare class CloudBackend implements Backend {
|
|
|
27
27
|
private readonly config;
|
|
28
28
|
constructor(config: CloudBackendConfig);
|
|
29
29
|
addPackageToWorkspace(input: Parameters<typeof packagesWorkspace.addPackageToWorkspace>[1]): Promise<import("../common.js").AddPackageToWorkspaceOutput>;
|
|
30
|
+
cancelExecutorJob(input: {
|
|
31
|
+
jobId: string;
|
|
32
|
+
workspace: string;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
reason: string;
|
|
35
|
+
status?: string;
|
|
36
|
+
success: false;
|
|
37
|
+
} | {
|
|
38
|
+
status: string;
|
|
39
|
+
success: true;
|
|
40
|
+
}>;
|
|
30
41
|
createWorkspace(input: Parameters<typeof workspaces.createWorkspace>[1]): Promise<import("../common.js").CreateWorkspaceOutput>;
|
|
31
42
|
deleteState(input: Parameters<typeof state.deleteState>[1]): Promise<import("../common.js").DeleteStateOutput>;
|
|
32
43
|
deleteWorkspace(input: Parameters<typeof workspaces.deleteWorkspace>[1]): Promise<import("../common.js").DeleteWorkspaceOutput>;
|
|
@@ -124,6 +135,25 @@ export declare class CloudBackend implements Backend {
|
|
|
124
135
|
success: true;
|
|
125
136
|
versions: appsVersions.AppVersionSummary[];
|
|
126
137
|
}>;
|
|
138
|
+
listExecutorJobs(input: {
|
|
139
|
+
limit?: number;
|
|
140
|
+
status?: string;
|
|
141
|
+
workspace: string;
|
|
142
|
+
}): Promise<{
|
|
143
|
+
jobs: Array<{
|
|
144
|
+
attempts: number;
|
|
145
|
+
claimedBy: null | string;
|
|
146
|
+
createdAt: string;
|
|
147
|
+
id: string;
|
|
148
|
+
status: string;
|
|
149
|
+
type: string;
|
|
150
|
+
updatedAt: string;
|
|
151
|
+
}>;
|
|
152
|
+
success: true;
|
|
153
|
+
} | {
|
|
154
|
+
reason: string;
|
|
155
|
+
success: false;
|
|
156
|
+
}>;
|
|
127
157
|
listPackageVersions(name: string): Promise<import("../common.js").ListPackageVersionsOutput>;
|
|
128
158
|
listProjects(): Promise<import("../common.js").ListProjectsOutput>;
|
|
129
159
|
listWorkspaces(input?: Parameters<typeof workspaces.listWorkspaces>[1]): Promise<string[]>;
|
|
@@ -162,6 +192,15 @@ export declare class CloudBackend implements Backend {
|
|
|
162
192
|
} | {
|
|
163
193
|
success: true;
|
|
164
194
|
}>;
|
|
195
|
+
rewakeExecutorJob(input: {
|
|
196
|
+
jobId: string;
|
|
197
|
+
workspace: string;
|
|
198
|
+
}): Promise<{
|
|
199
|
+
reason?: string;
|
|
200
|
+
rewoken?: boolean;
|
|
201
|
+
status?: string;
|
|
202
|
+
success: boolean;
|
|
203
|
+
}>;
|
|
165
204
|
saveProjectMetadata(input: Parameters<typeof projects.saveProjectMetadata>[1]): Promise<import("../common.js").SaveProjectMetadataOutput>;
|
|
166
205
|
saveState(config: Config, workspace?: string): Promise<void>;
|
|
167
206
|
searchPackages(input: Parameters<typeof packagesRegistry.searchPackages>[1]): Promise<import("../common.js").SearchPackagesOutput>;
|
|
@@ -18,6 +18,9 @@ export class CloudBackend {
|
|
|
18
18
|
addPackageToWorkspace(input) {
|
|
19
19
|
return packagesWorkspace.addPackageToWorkspace(this.config, input);
|
|
20
20
|
}
|
|
21
|
+
cancelExecutorJob(input) {
|
|
22
|
+
return executorJobs.cancelExecutorJob(this.config, input);
|
|
23
|
+
}
|
|
21
24
|
createWorkspace(input) {
|
|
22
25
|
return workspaces.createWorkspace(this.config, input);
|
|
23
26
|
}
|
|
@@ -90,6 +93,9 @@ export class CloudBackend {
|
|
|
90
93
|
listAppVersions(name) {
|
|
91
94
|
return appsVersions.listAppVersions(this.config, name);
|
|
92
95
|
}
|
|
96
|
+
listExecutorJobs(input) {
|
|
97
|
+
return executorJobs.listExecutorJobs(this.config, input);
|
|
98
|
+
}
|
|
93
99
|
listPackageVersions(name) {
|
|
94
100
|
return packagesRegistry.listPackageVersions(this.config, name);
|
|
95
101
|
}
|
|
@@ -120,6 +126,9 @@ export class CloudBackend {
|
|
|
120
126
|
revokeExecutorToken(input) {
|
|
121
127
|
return executorJobs.revokeExecutorToken(this.config, input);
|
|
122
128
|
}
|
|
129
|
+
rewakeExecutorJob(input) {
|
|
130
|
+
return executorJobs.rewakeExecutorJob(this.config, input);
|
|
131
|
+
}
|
|
123
132
|
saveProjectMetadata(input) {
|
|
124
133
|
return projects.saveProjectMetadata(this.config, input);
|
|
125
134
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class JobCancel extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
jobId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { CloudBackend } from '../../../backend/cloud/cloud-backend.js';
|
|
3
|
+
import { getBackend } from '../../../backend/index.js';
|
|
4
|
+
export default class JobCancel extends Command {
|
|
5
|
+
static args = {
|
|
6
|
+
jobId: Args.string({ description: 'id of the remote executor job to cancel', required: true }),
|
|
7
|
+
};
|
|
8
|
+
static description = 'Cancel a pending remote executor job. Only pending jobs can be cancelled.';
|
|
9
|
+
static examples = ['<%= config.bin %> <%= command.id %> my-job-id -w my-workspace'];
|
|
10
|
+
static flags = {
|
|
11
|
+
workspace: Flags.string({
|
|
12
|
+
char: 'w',
|
|
13
|
+
description: 'name of the workspace the job belongs to',
|
|
14
|
+
required: true,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { args, flags } = await this.parse(JobCancel);
|
|
19
|
+
const backend = await getBackend();
|
|
20
|
+
if (!(backend instanceof CloudBackend)) {
|
|
21
|
+
this.error('Cancelling executor jobs requires the cloud backend. Run `hereya login` first.');
|
|
22
|
+
}
|
|
23
|
+
const cloudBackend = backend;
|
|
24
|
+
const result = await cloudBackend.cancelExecutorJob({ jobId: args.jobId, workspace: flags.workspace });
|
|
25
|
+
if (!result.success) {
|
|
26
|
+
const detail = result.status && !result.reason.includes(result.status) ? ` (current status: ${result.status})` : '';
|
|
27
|
+
this.error(`Failed to cancel job ${args.jobId}: ${result.reason}${detail}`);
|
|
28
|
+
}
|
|
29
|
+
this.log(`Job ${args.jobId} cancelled.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class JobList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { CloudBackend } from '../../../backend/cloud/cloud-backend.js';
|
|
3
|
+
import { getBackend } from '../../../backend/index.js';
|
|
4
|
+
export default class JobList extends Command {
|
|
5
|
+
static description = 'List remote executor jobs for a workspace, newest first. Pending jobs can be cancelled with `hereya job cancel <jobId>`.';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> -w my-workspace',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> -w my-workspace --status pending --limit 10',
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
limit: Flags.integer({
|
|
12
|
+
description: 'maximum number of jobs to list (server default: 20)',
|
|
13
|
+
required: false,
|
|
14
|
+
}),
|
|
15
|
+
status: Flags.string({
|
|
16
|
+
description: 'only list jobs with this status',
|
|
17
|
+
options: ['pending', 'running', 'completed', 'failed', 'cancelled'],
|
|
18
|
+
required: false,
|
|
19
|
+
}),
|
|
20
|
+
workspace: Flags.string({
|
|
21
|
+
char: 'w',
|
|
22
|
+
description: 'name of the workspace to list jobs for',
|
|
23
|
+
required: true,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(JobList);
|
|
28
|
+
const backend = await getBackend();
|
|
29
|
+
if (!(backend instanceof CloudBackend)) {
|
|
30
|
+
this.error('Listing executor jobs requires the cloud backend. Run `hereya login` first.');
|
|
31
|
+
}
|
|
32
|
+
const cloudBackend = backend;
|
|
33
|
+
const result = await cloudBackend.listExecutorJobs({
|
|
34
|
+
limit: flags.limit,
|
|
35
|
+
status: flags.status,
|
|
36
|
+
workspace: flags.workspace,
|
|
37
|
+
});
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
this.error(`Failed to list jobs: ${result.reason}`);
|
|
40
|
+
}
|
|
41
|
+
if (result.jobs.length === 0) {
|
|
42
|
+
this.log(`No jobs found for workspace ${flags.workspace}.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const idWidth = Math.max(2, ...result.jobs.map((j) => j.id.length));
|
|
46
|
+
const typeWidth = Math.max(4, ...result.jobs.map((j) => j.type.length));
|
|
47
|
+
const statusWidth = Math.max(6, ...result.jobs.map((j) => j.status.length));
|
|
48
|
+
this.log(`${'Id'.padEnd(idWidth)} ${'Type'.padEnd(typeWidth)} ${'Status'.padEnd(statusWidth)} Created`);
|
|
49
|
+
this.log('-'.repeat(idWidth + typeWidth + statusWidth + 13));
|
|
50
|
+
for (const job of result.jobs) {
|
|
51
|
+
this.log(`${job.id.padEnd(idWidth)} ${job.type.padEnd(typeWidth)} ${job.status.padEnd(statusWidth)} ${new Date(job.createdAt).toLocaleString()}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
const DEFAULT_TIMEOUT_MIN = 90;
|
|
2
2
|
const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000;
|
|
3
|
+
/**
|
|
4
|
+
* How often, while a job is still `pending`, the CLI asks hereya-cloud to
|
|
5
|
+
* re-fire the executor wake. Guards against a wake being swallowed because the
|
|
6
|
+
* ephemeral executor was idle-draining when the job was created.
|
|
7
|
+
*/
|
|
8
|
+
const REWAKE_INTERVAL_MS = 60_000;
|
|
9
|
+
/**
|
|
10
|
+
* Grace period before the first re-wake, giving the original creation-time wake
|
|
11
|
+
* a chance to land before we ask for another one.
|
|
12
|
+
*/
|
|
13
|
+
const REWAKE_INITIAL_DELAY_MS = 60_000;
|
|
3
14
|
/**
|
|
4
15
|
* Default poll timeout, in ms. Overridable via HEREYA_JOB_TIMEOUT_MIN env var
|
|
5
16
|
* (positive integer, in minutes). Falls back to DEFAULT_TIMEOUT_MIN.
|
|
@@ -14,14 +25,39 @@ function defaultTimeoutMs() {
|
|
|
14
25
|
}
|
|
15
26
|
return DEFAULT_TIMEOUT_MIN * 60 * 1000;
|
|
16
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* While a job is still `pending` (not yet claimed by any executor), periodically
|
|
30
|
+
* ask hereya-cloud to re-fire the executor wake. Recovers from a creation-time
|
|
31
|
+
* wake being swallowed by an idle-draining ephemeral executor. Any failure is
|
|
32
|
+
* non-fatal — the caller keeps polling regardless. Returns the (possibly
|
|
33
|
+
* updated) `lastRewakeAt` timestamp.
|
|
34
|
+
*/
|
|
35
|
+
async function maybeRewake(args) {
|
|
36
|
+
const { cloudBackend, jobId, lastRewakeAt, logger, startTime, status, workspace } = args;
|
|
37
|
+
if (status !== 'pending') {
|
|
38
|
+
return lastRewakeAt;
|
|
39
|
+
}
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (now - startTime < REWAKE_INITIAL_DELAY_MS || now - lastRewakeAt < REWAKE_INTERVAL_MS) {
|
|
42
|
+
return lastRewakeAt;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await cloudBackend.rewakeExecutorJob({ jobId, workspace });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger?.debug?.(`Re-wake request failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
}
|
|
50
|
+
return now;
|
|
51
|
+
}
|
|
17
52
|
export async function pollExecutorJob(input) {
|
|
18
53
|
const { cloudBackend, jobId, logger, workspace } = input;
|
|
19
54
|
const timeout = input.timeoutMs ?? defaultTimeoutMs();
|
|
20
|
-
logger?.info?.(`Remote executor job submitted (${jobId}). Waiting for result
|
|
55
|
+
logger?.info?.(`Remote executor job submitted (${jobId}). Waiting for result... Cancel with: hereya job cancel ${jobId} -w ${workspace}`);
|
|
21
56
|
const startTime = Date.now();
|
|
22
57
|
let lastLogLength = 0;
|
|
23
58
|
let lastStatus = 'pending';
|
|
24
59
|
let lastUserHeartbeat = startTime;
|
|
60
|
+
let lastRewakeAt = startTime;
|
|
25
61
|
while (Date.now() - startTime < timeout) {
|
|
26
62
|
// eslint-disable-next-line no-await-in-loop
|
|
27
63
|
const statusResult = await cloudBackend.getExecutorJobStatus({
|
|
@@ -53,6 +89,16 @@ export async function pollExecutorJob(input) {
|
|
|
53
89
|
lastUserHeartbeat = now;
|
|
54
90
|
}
|
|
55
91
|
}
|
|
92
|
+
// eslint-disable-next-line no-await-in-loop
|
|
93
|
+
lastRewakeAt = await maybeRewake({
|
|
94
|
+
cloudBackend,
|
|
95
|
+
jobId,
|
|
96
|
+
lastRewakeAt,
|
|
97
|
+
logger,
|
|
98
|
+
startTime,
|
|
99
|
+
status: job.status,
|
|
100
|
+
workspace,
|
|
101
|
+
});
|
|
56
102
|
if (job.status === 'completed') {
|
|
57
103
|
if (job.result) {
|
|
58
104
|
return { result: job.result, success: true };
|
|
@@ -63,6 +109,9 @@ export async function pollExecutorJob(input) {
|
|
|
63
109
|
const reason = job.result?.reason || 'Remote executor job failed';
|
|
64
110
|
return { reason, success: false };
|
|
65
111
|
}
|
|
112
|
+
if (job.status === 'cancelled') {
|
|
113
|
+
return { reason: 'Job was cancelled', success: false };
|
|
114
|
+
}
|
|
66
115
|
// Small delay between polls (server-side long polling handles most of the wait)
|
|
67
116
|
// eslint-disable-next-line no-await-in-loop
|
|
68
117
|
await new Promise(resolve => {
|