@proagentstore/cli 0.1.7 → 0.1.9
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/browser-runner/index.js +3 -3
- package/dist/browser-runner/runner.js +256 -39
- package/dist/browser-runner/server.js +3 -1
- package/dist/browser-runner/test-job-server-auth-cli.js +44 -0
- package/dist/browser-runner/test-job-server-auth.js +450 -0
- package/dist/chunk-LNY7MNUC.js +423 -0
- package/dist/index.js +204 -397
- package/dist/runner-CM72EPKQ.js +34 -0
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ function configFromArgs() {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
if (flag("--help") || flag("-h")) {
|
|
28
|
-
process.stdout.write(`
|
|
28
|
+
process.stdout.write(`FreeAgentStore browser runtime for ProAgentStore
|
|
29
29
|
|
|
30
30
|
Usage:
|
|
31
31
|
pags-browser-runner [--host 127.0.0.1] [--port 49171] [--data-dir path] [--token token] [--instance-id id] [--headless]
|
|
@@ -44,9 +44,9 @@ Endpoints:
|
|
|
44
44
|
}
|
|
45
45
|
const config = configFromArgs();
|
|
46
46
|
const started = await startRunnerServer(config);
|
|
47
|
-
process.stdout.write(`
|
|
47
|
+
process.stdout.write(`FAGS browser runtime listening at ${started.url}\n`);
|
|
48
48
|
process.stdout.write(`Data dir: ${config.dataDir}\n`);
|
|
49
|
-
process.stdout.write(
|
|
49
|
+
process.stdout.write("Control plane: PAGS; runtime plane: FAGS; brain placement: PAGS; runner role: tool-executor\n");
|
|
50
50
|
if (config.token)
|
|
51
51
|
process.stdout.write("Auth: bearer token required\n");
|
|
52
52
|
if (config.instanceId)
|
|
@@ -11,7 +11,7 @@ const CAPABILITIES = [
|
|
|
11
11
|
"file.upload",
|
|
12
12
|
"human.approval",
|
|
13
13
|
];
|
|
14
|
-
const APPROVAL_REQUIRED_TASKS = new Set(["browser.open", "job.apply_basic"]);
|
|
14
|
+
const APPROVAL_REQUIRED_TASKS = new Set(["browser.open", "job.apply_basic", "job.apply_authenticated"]);
|
|
15
15
|
const require = createRequire(import.meta.url);
|
|
16
16
|
export class RunnerInputError extends Error {
|
|
17
17
|
status = 400;
|
|
@@ -28,11 +28,13 @@ export class LocalRunner {
|
|
|
28
28
|
}
|
|
29
29
|
capabilities() {
|
|
30
30
|
return {
|
|
31
|
-
runtime: "
|
|
31
|
+
runtime: "fags-browser-runtime",
|
|
32
32
|
brainPlacement: "pags",
|
|
33
|
+
controlPlane: "pags",
|
|
34
|
+
runtimePlane: "fags",
|
|
33
35
|
runnerRole: "tool-executor",
|
|
34
36
|
capabilities: CAPABILITIES,
|
|
35
|
-
taskTypes: ["echo", "browser.open", "job.apply_basic"],
|
|
37
|
+
taskTypes: ["echo", "browser.open", "job.apply_basic", "job.apply_authenticated"],
|
|
36
38
|
approvalRequiredFor: [...APPROVAL_REQUIRED_TASKS],
|
|
37
39
|
};
|
|
38
40
|
}
|
|
@@ -55,11 +57,8 @@ export class LocalRunner {
|
|
|
55
57
|
updatedAt: now,
|
|
56
58
|
};
|
|
57
59
|
this.store.putTask(task);
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
type: "task.created",
|
|
61
|
-
message: `Task created: ${task.type}`,
|
|
62
|
-
data: { status: task.status },
|
|
60
|
+
this.addTaskEvent(task, "task.created", `Task created: ${task.type}`, {
|
|
61
|
+
status: task.status,
|
|
63
62
|
});
|
|
64
63
|
if (task.status === "queued")
|
|
65
64
|
void this.runTask(task.id);
|
|
@@ -77,11 +76,7 @@ export class LocalRunner {
|
|
|
77
76
|
approvedAt: task.updatedAt,
|
|
78
77
|
};
|
|
79
78
|
this.store.putTask(task);
|
|
80
|
-
this.
|
|
81
|
-
taskId: task.id,
|
|
82
|
-
type: "task.approved",
|
|
83
|
-
message: `Task approved: ${task.type}`,
|
|
84
|
-
});
|
|
79
|
+
this.addTaskEvent(task, "task.approved", `Task approved: ${task.type}`);
|
|
85
80
|
await this.runTask(task.id);
|
|
86
81
|
return this.requireTask(id);
|
|
87
82
|
}
|
|
@@ -93,11 +88,7 @@ export class LocalRunner {
|
|
|
93
88
|
task.updatedAt = new Date().toISOString();
|
|
94
89
|
task.completedAt = task.updatedAt;
|
|
95
90
|
this.store.putTask(task);
|
|
96
|
-
this.
|
|
97
|
-
taskId: task.id,
|
|
98
|
-
type: "task.cancelled",
|
|
99
|
-
message: `Task cancelled: ${task.type}`,
|
|
100
|
-
});
|
|
91
|
+
this.addTaskEvent(task, "task.cancelled", `Task cancelled: ${task.type}`);
|
|
101
92
|
return task;
|
|
102
93
|
}
|
|
103
94
|
async close() {
|
|
@@ -111,11 +102,7 @@ export class LocalRunner {
|
|
|
111
102
|
task.status = "running";
|
|
112
103
|
task.updatedAt = new Date().toISOString();
|
|
113
104
|
this.store.putTask(task);
|
|
114
|
-
this.
|
|
115
|
-
taskId: task.id,
|
|
116
|
-
type: "task.running",
|
|
117
|
-
message: `Task running: ${task.type}`,
|
|
118
|
-
});
|
|
105
|
+
this.addTaskEvent(task, "task.running", `Task running: ${task.type}`);
|
|
119
106
|
try {
|
|
120
107
|
const output = await this.execute(task);
|
|
121
108
|
task.status = "completed";
|
|
@@ -123,12 +110,7 @@ export class LocalRunner {
|
|
|
123
110
|
task.updatedAt = new Date().toISOString();
|
|
124
111
|
task.completedAt = task.updatedAt;
|
|
125
112
|
this.store.putTask(task);
|
|
126
|
-
this.
|
|
127
|
-
taskId: task.id,
|
|
128
|
-
type: "task.completed",
|
|
129
|
-
message: `Task completed: ${task.type}`,
|
|
130
|
-
data: output,
|
|
131
|
-
});
|
|
113
|
+
this.addTaskEvent(task, "task.completed", `Task completed: ${task.type}`, output);
|
|
132
114
|
}
|
|
133
115
|
catch (error) {
|
|
134
116
|
task.status = "failed";
|
|
@@ -136,13 +118,17 @@ export class LocalRunner {
|
|
|
136
118
|
task.updatedAt = new Date().toISOString();
|
|
137
119
|
task.completedAt = task.updatedAt;
|
|
138
120
|
this.store.putTask(task);
|
|
139
|
-
this.
|
|
140
|
-
taskId: task.id,
|
|
141
|
-
type: "task.failed",
|
|
142
|
-
message: task.error,
|
|
143
|
-
});
|
|
121
|
+
this.addTaskEvent(task, "task.failed", task.error);
|
|
144
122
|
}
|
|
145
123
|
}
|
|
124
|
+
addTaskEvent(task, type, message, data) {
|
|
125
|
+
this.store.addEvent({
|
|
126
|
+
taskId: task.id,
|
|
127
|
+
type,
|
|
128
|
+
message,
|
|
129
|
+
data,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
146
132
|
async execute(task) {
|
|
147
133
|
if (task.type === "echo") {
|
|
148
134
|
return task.input;
|
|
@@ -154,32 +140,59 @@ export class LocalRunner {
|
|
|
154
140
|
}
|
|
155
141
|
const context = await this.getBrowserContext();
|
|
156
142
|
const page = context.pages()[0] || (await context.newPage());
|
|
143
|
+
this.addTaskEvent(task, "browser.goto.started", "Opening browser page", { url });
|
|
157
144
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
158
|
-
|
|
145
|
+
const output = {
|
|
159
146
|
url: page.url(),
|
|
160
147
|
title: await page.title(),
|
|
161
148
|
};
|
|
149
|
+
this.addTaskEvent(task, "browser.goto.completed", "Browser page loaded", output);
|
|
150
|
+
return output;
|
|
162
151
|
}
|
|
163
152
|
if (task.type === "job.apply_basic") {
|
|
164
|
-
return this.applyToBasicJob(task
|
|
153
|
+
return this.applyToBasicJob(task);
|
|
154
|
+
}
|
|
155
|
+
if (task.type === "job.apply_authenticated") {
|
|
156
|
+
return this.applyToAuthenticatedJob(task);
|
|
165
157
|
}
|
|
166
158
|
throw new Error(`Unknown task type: ${task.type}`);
|
|
167
159
|
}
|
|
168
|
-
async applyToBasicJob(
|
|
169
|
-
const job = normalizeJobApplicationInput(input);
|
|
160
|
+
async applyToBasicJob(task) {
|
|
161
|
+
const job = normalizeJobApplicationInput(task.input);
|
|
170
162
|
const context = await this.getBrowserContext();
|
|
171
163
|
const page = context.pages()[0] || (await context.newPage());
|
|
164
|
+
this.addTaskEvent(task, "browser.goto.started", "Opening job application page", {
|
|
165
|
+
url: job.url,
|
|
166
|
+
});
|
|
172
167
|
await page.goto(job.url, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
168
|
+
this.addTaskEvent(task, "browser.goto.completed", "Job application page loaded", {
|
|
169
|
+
url: page.url(),
|
|
170
|
+
title: await page.title(),
|
|
171
|
+
});
|
|
172
|
+
this.addTaskEvent(task, "job.form.fill.started", "Filling application form", {
|
|
173
|
+
candidate: {
|
|
174
|
+
fullName: job.candidate.fullName,
|
|
175
|
+
email: job.candidate.email,
|
|
176
|
+
},
|
|
177
|
+
resumeFile: basename(job.resumePath),
|
|
178
|
+
});
|
|
173
179
|
const filledFields = await fillBasicApplicationForm(page, job);
|
|
180
|
+
this.addTaskEvent(task, "job.form.filled", "Application form fields completed", {
|
|
181
|
+
fieldsFilled: filledFields,
|
|
182
|
+
resumeFile: basename(job.resumePath),
|
|
183
|
+
});
|
|
174
184
|
const beforeSubmitUrl = page.url();
|
|
175
185
|
const navigation = page.waitForNavigation({
|
|
176
186
|
waitUntil: "domcontentloaded",
|
|
177
187
|
timeout: 15_000,
|
|
178
188
|
}).catch(() => null);
|
|
189
|
+
this.addTaskEvent(task, "job.form.submit.started", "Submitting application form", {
|
|
190
|
+
url: beforeSubmitUrl,
|
|
191
|
+
});
|
|
179
192
|
await submitApplicationForm(page);
|
|
180
193
|
await navigation;
|
|
181
194
|
await page.waitForLoadState("domcontentloaded", { timeout: 5_000 }).catch(() => undefined);
|
|
182
|
-
|
|
195
|
+
const output = {
|
|
183
196
|
taskType: "job.apply_basic",
|
|
184
197
|
submitted: true,
|
|
185
198
|
beforeSubmitUrl,
|
|
@@ -189,6 +202,114 @@ export class LocalRunner {
|
|
|
189
202
|
resumeFile: basename(job.resumePath),
|
|
190
203
|
visibleText: (await page.locator("body").innerText({ timeout: 5_000 }).catch(() => "")).slice(0, 2_000),
|
|
191
204
|
};
|
|
205
|
+
this.addTaskEvent(task, "job.form.submit.completed", "Application submission completed", output);
|
|
206
|
+
return output;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Apply to a job that requires login/registration first.
|
|
210
|
+
* Flow: navigate → detect login requirement → register or login → fill form → submit
|
|
211
|
+
*/
|
|
212
|
+
async applyToAuthenticatedJob(task) {
|
|
213
|
+
const input = normalizeAuthenticatedJobInput(task.input);
|
|
214
|
+
const context = await this.getBrowserContext();
|
|
215
|
+
const page = context.pages()[0] || (await context.newPage());
|
|
216
|
+
// Step 1: Navigate to job page
|
|
217
|
+
this.addTaskEvent(task, "browser.goto.started", "Opening job page", { url: input.url });
|
|
218
|
+
await page.goto(input.url, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
219
|
+
this.addTaskEvent(task, "browser.goto.completed", "Job page loaded", {
|
|
220
|
+
url: page.url(),
|
|
221
|
+
title: await page.title(),
|
|
222
|
+
});
|
|
223
|
+
// Step 2: Check if we need to authenticate
|
|
224
|
+
const needsAuth = await detectAuthRequirement(page);
|
|
225
|
+
if (needsAuth) {
|
|
226
|
+
this.addTaskEvent(task, "job.auth.required", "Authentication required", {
|
|
227
|
+
loginDetected: true,
|
|
228
|
+
});
|
|
229
|
+
// Navigate to login page
|
|
230
|
+
const loginUrl = input.loginUrl || await findLoginUrl(page, input.url);
|
|
231
|
+
if (!loginUrl)
|
|
232
|
+
throw new Error("Login required but no login URL found");
|
|
233
|
+
await page.goto(loginUrl, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
234
|
+
if (input.registration && !input.accountExists) {
|
|
235
|
+
// Register first
|
|
236
|
+
this.addTaskEvent(task, "job.auth.register.started", "Registering new account");
|
|
237
|
+
const registerUrl = await findRegisterUrl(page, loginUrl);
|
|
238
|
+
if (registerUrl && registerUrl !== loginUrl) {
|
|
239
|
+
await page.goto(registerUrl, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
240
|
+
}
|
|
241
|
+
await fillRegistrationForm(page, input.registration);
|
|
242
|
+
await submitForm(page);
|
|
243
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 10_000 }).catch(() => undefined);
|
|
244
|
+
this.addTaskEvent(task, "job.auth.register.completed", "Registration submitted", {
|
|
245
|
+
url: page.url(),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else if (input.credentials) {
|
|
249
|
+
// Login with existing credentials
|
|
250
|
+
this.addTaskEvent(task, "job.auth.login.started", "Logging in");
|
|
251
|
+
await fillLoginForm(page, input.credentials);
|
|
252
|
+
await submitForm(page);
|
|
253
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 10_000 }).catch(() => undefined);
|
|
254
|
+
this.addTaskEvent(task, "job.auth.login.completed", "Login submitted", {
|
|
255
|
+
url: page.url(),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
throw new Error("Login required but no credentials or registration details provided");
|
|
260
|
+
}
|
|
261
|
+
// Verify we're logged in (check for common indicators)
|
|
262
|
+
const stillNeedsAuth = await detectAuthRequirement(page);
|
|
263
|
+
if (stillNeedsAuth) {
|
|
264
|
+
// Try navigating back to the apply page — some sites redirect after login
|
|
265
|
+
const applyUrl = input.url.includes("/apply") ? input.url : `${input.url}`;
|
|
266
|
+
await page.goto(applyUrl, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
267
|
+
const thirdCheck = await detectAuthRequirement(page);
|
|
268
|
+
if (thirdCheck) {
|
|
269
|
+
throw new Error("Authentication failed — still seeing login/register prompts after login attempt");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Navigate to apply page (may have been redirected after login)
|
|
273
|
+
if (!page.url().includes("apply")) {
|
|
274
|
+
await page.goto(input.url, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Step 3: Look for the apply form (might be on current page or linked)
|
|
278
|
+
const applyLink = await page.locator('a[href*="apply"], a:has-text("Apply")').first();
|
|
279
|
+
if (await applyLink.count() > 0) {
|
|
280
|
+
await applyLink.click();
|
|
281
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 10_000 }).catch(() => undefined);
|
|
282
|
+
}
|
|
283
|
+
// Step 4: Fill the application form
|
|
284
|
+
this.addTaskEvent(task, "job.form.fill.started", "Filling application form");
|
|
285
|
+
const filledFields = await fillBasicApplicationForm(page, input);
|
|
286
|
+
this.addTaskEvent(task, "job.form.filled", "Form fields completed", {
|
|
287
|
+
fieldsFilled: filledFields,
|
|
288
|
+
resumeFile: basename(input.resumePath),
|
|
289
|
+
});
|
|
290
|
+
// Step 5: Submit
|
|
291
|
+
const beforeSubmitUrl = page.url();
|
|
292
|
+
const navigation = page.waitForNavigation({
|
|
293
|
+
waitUntil: "domcontentloaded",
|
|
294
|
+
timeout: 15_000,
|
|
295
|
+
}).catch(() => null);
|
|
296
|
+
this.addTaskEvent(task, "job.form.submit.started", "Submitting application");
|
|
297
|
+
await submitApplicationForm(page);
|
|
298
|
+
await navigation;
|
|
299
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 5_000 }).catch(() => undefined);
|
|
300
|
+
const output = {
|
|
301
|
+
taskType: "job.apply_authenticated",
|
|
302
|
+
submitted: true,
|
|
303
|
+
authenticated: needsAuth,
|
|
304
|
+
beforeSubmitUrl,
|
|
305
|
+
finalUrl: page.url(),
|
|
306
|
+
title: await page.title(),
|
|
307
|
+
fieldsFilled: filledFields,
|
|
308
|
+
resumeFile: basename(input.resumePath),
|
|
309
|
+
visibleText: (await page.locator("body").innerText({ timeout: 5_000 }).catch(() => "")).slice(0, 2_000),
|
|
310
|
+
};
|
|
311
|
+
this.addTaskEvent(task, "job.form.submit.completed", "Application submitted", output);
|
|
312
|
+
return output;
|
|
192
313
|
}
|
|
193
314
|
requireTask(id) {
|
|
194
315
|
const task = this.store.getTask(id);
|
|
@@ -404,3 +525,99 @@ function normalizeCreateTaskRequest(request) {
|
|
|
404
525
|
export function fileUrl(path) {
|
|
405
526
|
return pathToFileURL(path).toString();
|
|
406
527
|
}
|
|
528
|
+
// ── Authenticated job application helpers ─────────────────────────────────
|
|
529
|
+
export function normalizeAuthenticatedJobInput(input) {
|
|
530
|
+
const base = normalizeJobApplicationInput(input);
|
|
531
|
+
const credentials = isRecord(input.credentials) ? input.credentials : undefined;
|
|
532
|
+
const registration = isRecord(input.registration) ? input.registration : undefined;
|
|
533
|
+
return {
|
|
534
|
+
...base,
|
|
535
|
+
credentials: credentials ? {
|
|
536
|
+
email: stringValue(credentials.email),
|
|
537
|
+
password: stringValue(credentials.password),
|
|
538
|
+
} : undefined,
|
|
539
|
+
loginUrl: optionalString(input.loginUrl),
|
|
540
|
+
accountExists: input.accountExists === true,
|
|
541
|
+
registration: registration ? {
|
|
542
|
+
fullName: stringValue(registration.fullName),
|
|
543
|
+
email: stringValue(registration.email),
|
|
544
|
+
password: stringValue(registration.password),
|
|
545
|
+
} : undefined,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
async function detectAuthRequirement(page) {
|
|
549
|
+
const body = await page.locator("body").innerHTML({ timeout: 5_000 }).catch(() => "");
|
|
550
|
+
const lower = body.toLowerCase();
|
|
551
|
+
// Check for login/register prompts
|
|
552
|
+
const hasLoginForm = await page.locator('input[type="password"], form[action*="login"], form[action*="signin"]').count() > 0;
|
|
553
|
+
const hasLoginText = /sign\s*in|log\s*in|create\s*(an\s*)?account|register\s*to\s*apply/i.test(lower);
|
|
554
|
+
const hasLoginLink = await page.locator('a[href*="login"], a[href*="signin"], a[href*="register"]').count() > 0;
|
|
555
|
+
return hasLoginForm || (hasLoginText && hasLoginLink);
|
|
556
|
+
}
|
|
557
|
+
async function findLoginUrl(page, baseUrl) {
|
|
558
|
+
const loginLink = page.locator('a[href*="login"], a[href*="signin"], a:has-text("Sign in"), a:has-text("Log in")').first();
|
|
559
|
+
if (await loginLink.count() > 0) {
|
|
560
|
+
const href = await loginLink.getAttribute("href");
|
|
561
|
+
if (href)
|
|
562
|
+
return new URL(href, baseUrl).toString();
|
|
563
|
+
}
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
async function findRegisterUrl(page, baseUrl) {
|
|
567
|
+
const regLink = page.locator('a[href*="register"], a[href*="signup"], a:has-text("Create account"), a:has-text("Sign up")').first();
|
|
568
|
+
if (await regLink.count() > 0) {
|
|
569
|
+
const href = await regLink.getAttribute("href");
|
|
570
|
+
if (href)
|
|
571
|
+
return new URL(href, baseUrl).toString();
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
async function fillLoginForm(page, credentials) {
|
|
576
|
+
await fillFirst(page, [
|
|
577
|
+
'input[name="email"]',
|
|
578
|
+
'input[type="email"]',
|
|
579
|
+
'input[autocomplete="email"]',
|
|
580
|
+
'input[autocomplete="username"]',
|
|
581
|
+
'input[name="username"]',
|
|
582
|
+
], credentials.email);
|
|
583
|
+
await fillFirst(page, [
|
|
584
|
+
'input[name="password"]',
|
|
585
|
+
'input[type="password"]',
|
|
586
|
+
'input[autocomplete="current-password"]',
|
|
587
|
+
], credentials.password);
|
|
588
|
+
}
|
|
589
|
+
async function fillRegistrationForm(page, registration) {
|
|
590
|
+
await fillFirst(page, [
|
|
591
|
+
'input[name="fullName"]',
|
|
592
|
+
'input[name="full_name"]',
|
|
593
|
+
'input[name="name"]',
|
|
594
|
+
'input[autocomplete="name"]',
|
|
595
|
+
], registration.fullName);
|
|
596
|
+
await fillFirst(page, [
|
|
597
|
+
'input[name="email"]',
|
|
598
|
+
'input[type="email"]',
|
|
599
|
+
'input[autocomplete="email"]',
|
|
600
|
+
], registration.email);
|
|
601
|
+
await fillFirst(page, [
|
|
602
|
+
'input[name="password"]',
|
|
603
|
+
'input[type="password"]',
|
|
604
|
+
'input[autocomplete="new-password"]',
|
|
605
|
+
], registration.password);
|
|
606
|
+
}
|
|
607
|
+
async function submitForm(page) {
|
|
608
|
+
const submit = page.locator('button[type="submit"], input[type="submit"]').first();
|
|
609
|
+
if (await submit.count() > 0) {
|
|
610
|
+
await submit.click({ timeout: 10_000 });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const form = page.locator("form").first();
|
|
614
|
+
if (await form.count() > 0) {
|
|
615
|
+
await form.evaluate((node) => {
|
|
616
|
+
const f = node;
|
|
617
|
+
if (typeof f.requestSubmit === "function")
|
|
618
|
+
f.requestSubmit();
|
|
619
|
+
else
|
|
620
|
+
f.submit();
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
@@ -42,8 +42,10 @@ async function route(runner, req, res) {
|
|
|
42
42
|
if (req.method === "GET" && path === "/health") {
|
|
43
43
|
return json(res, 200, {
|
|
44
44
|
ok: true,
|
|
45
|
-
service: "
|
|
45
|
+
service: "freeagentstore-browser-runtime",
|
|
46
46
|
brainPlacement: "pags",
|
|
47
|
+
controlPlane: "pags",
|
|
48
|
+
runtimePlane: "fags",
|
|
47
49
|
instanceId: runner.config.instanceId,
|
|
48
50
|
});
|
|
49
51
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startTestJobServerAuth } from "./test-job-server-auth.js";
|
|
3
|
+
function arg(name, fallback) {
|
|
4
|
+
const index = process.argv.indexOf(name);
|
|
5
|
+
if (index === -1)
|
|
6
|
+
return fallback;
|
|
7
|
+
return process.argv[index + 1] || fallback;
|
|
8
|
+
}
|
|
9
|
+
function flag(name) {
|
|
10
|
+
return process.argv.includes(name);
|
|
11
|
+
}
|
|
12
|
+
if (flag("--help") || flag("-h")) {
|
|
13
|
+
process.stdout.write(`ProAgentStore test job server (with auth)
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
pags-test-job-server-auth [--port 49211]
|
|
17
|
+
|
|
18
|
+
Routes:
|
|
19
|
+
GET /register Registration form
|
|
20
|
+
POST /register Create account
|
|
21
|
+
GET /login Login form
|
|
22
|
+
POST /login Sign in
|
|
23
|
+
POST /logout Sign out
|
|
24
|
+
GET /jobs/software-engineer Job posting (public)
|
|
25
|
+
GET /apply Application form (requires auth)
|
|
26
|
+
POST /apply Submit application (requires auth)
|
|
27
|
+
GET /dashboard User's applications (requires auth)
|
|
28
|
+
GET /success/:id Submission confirmation (requires auth)
|
|
29
|
+
GET /submissions All submissions (JSON, no auth)
|
|
30
|
+
`);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
const port = Number(arg("--port", process.env.PAGS_TEST_JOB_AUTH_PORT || "49211"));
|
|
34
|
+
const server = await startTestJobServerAuth(port);
|
|
35
|
+
process.stdout.write(`PAGS test job server (auth) listening at ${server.url}\n`);
|
|
36
|
+
process.stdout.write(`Job URL: ${server.jobUrl}\n`);
|
|
37
|
+
process.stdout.write(`Register: ${server.registerUrl}\n`);
|
|
38
|
+
process.stdout.write(`Login: ${server.loginUrl}\n`);
|
|
39
|
+
const shutdown = async () => {
|
|
40
|
+
await server.close();
|
|
41
|
+
process.exit(0);
|
|
42
|
+
};
|
|
43
|
+
process.on("SIGINT", () => void shutdown());
|
|
44
|
+
process.on("SIGTERM", () => void shutdown());
|