job-application-agent 3.5.0 → 3.6.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 +21 -0
- package/installer/src/cli.mjs +8 -1
- package/installer/src/installer.mjs +8 -0
- package/job-application-agent/SKILL.md +21 -0
- package/job-application-agent/capabilities.json +2 -1
- package/job-application-agent/references/AUTONOMY.md +2 -0
- package/job-application-agent/references/FREE_AI.md +39 -0
- package/job-application-agent/references/OUTREACH.md +210 -0
- package/job-application-agent/references/RUNS.md +29 -0
- package/job-application-agent/references/SCHEMAS.md +8 -2
- package/job-application-agent/references/agent-box/README.md +77 -0
- package/job-application-agent/references/agent-box/novnc.service.example +16 -0
- package/job-application-agent/scripts/ats/answer-inject.mjs +203 -0
- package/job-application-agent/scripts/ats/submit-adapters.mjs +194 -0
- package/job-application-agent/scripts/attention-questions.mjs +111 -0
- package/job-application-agent/scripts/attention-resume-submit.mjs +450 -0
- package/job-application-agent/scripts/attention-runner-poll.mjs +328 -0
- package/job-application-agent/scripts/captcha-vendor.mjs +328 -0
- package/job-application-agent/scripts/cloud-state-client.mjs +5 -1
- package/job-application-agent/scripts/job-application.mjs +117 -3
- package/job-application-agent/scripts/novnc-display-guard.mjs +300 -0
- package/job-application-agent/scripts/outreach-cli.mjs +95 -0
- package/job-application-agent/scripts/outreach-domain.mjs +287 -0
- package/job-application-agent/scripts/outreach-store.mjs +72 -0
- package/job-application-agent/scripts/session-binding.mjs +474 -0
- package/job-application-agent/scripts/version.mjs +1 -1
- package/job-application-agent/tests/answer-inject-captcha.test.mjs +169 -0
- package/job-application-agent/tests/attention-resume-submit.test.mjs +119 -0
- package/job-application-agent/tests/attention-runner-poll.test.mjs +135 -0
- package/job-application-agent/tests/fixtures/outreach.mjs +22 -0
- package/job-application-agent/tests/novnc-display-guard.test.mjs +50 -0
- package/job-application-agent/tests/outreach-cli.test.mjs +73 -0
- package/job-application-agent/tests/outreach.test.mjs +178 -0
- package/job-application-agent/tests/privacy-audit.test.mjs +2 -0
- package/job-application-agent/tests/session-binding.test.mjs +102 -0
- package/job-application-agent/tests/skill-contract.test.mjs +25 -0
- package/job-application-agent/tests/workflow-state.test.mjs +12 -1
- package/package.json +6 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
FILL_DISPLAY,
|
|
9
|
+
FILL_VNC_PORT,
|
|
10
|
+
FORBIDDEN_VNC_PORT,
|
|
11
|
+
assertSameTab,
|
|
12
|
+
createSessionBinding,
|
|
13
|
+
extractSessionBindingFields,
|
|
14
|
+
readSessionBindingFile,
|
|
15
|
+
sessionBindingPath,
|
|
16
|
+
validateSessionBinding,
|
|
17
|
+
writeSessionBindingFile,
|
|
18
|
+
} from "../scripts/session-binding.mjs";
|
|
19
|
+
|
|
20
|
+
test("validateSessionBinding requires fill display and VNC 5900", () => {
|
|
21
|
+
const ok = validateSessionBinding({
|
|
22
|
+
attentionId: "attention-1",
|
|
23
|
+
jobUrl: "https://jobs.ashbyhq.com/acme/application",
|
|
24
|
+
browserProfilePath: "/tmp/jaa-chrome",
|
|
25
|
+
});
|
|
26
|
+
assert.equal(ok.ok, true);
|
|
27
|
+
assert.equal(ok.binding.display, FILL_DISPLAY);
|
|
28
|
+
assert.equal(ok.binding.vncPort, FILL_VNC_PORT);
|
|
29
|
+
|
|
30
|
+
const badPort = validateSessionBinding({
|
|
31
|
+
attentionId: "attention-1",
|
|
32
|
+
jobUrl: "https://jobs.example.com/a",
|
|
33
|
+
browserProfilePath: "/tmp/p",
|
|
34
|
+
vncPort: FORBIDDEN_VNC_PORT,
|
|
35
|
+
});
|
|
36
|
+
assert.equal(badPort.ok, false);
|
|
37
|
+
assert.match(badPort.error, /5901/);
|
|
38
|
+
|
|
39
|
+
const badDisplay = validateSessionBinding({
|
|
40
|
+
attentionId: "attention-1",
|
|
41
|
+
jobUrl: "https://jobs.example.com/a",
|
|
42
|
+
browserProfilePath: "/tmp/p",
|
|
43
|
+
display: ":1",
|
|
44
|
+
});
|
|
45
|
+
assert.equal(badDisplay.ok, false);
|
|
46
|
+
assert.match(badDisplay.error, /forbidden|display/i);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("assertSameTab detects drift and accepts same application path", () => {
|
|
50
|
+
const same = assertSameTab(
|
|
51
|
+
"https://jobs.ashbyhq.com/acme/job/123",
|
|
52
|
+
"https://jobs.ashbyhq.com/acme/job/123?utm_source=x",
|
|
53
|
+
);
|
|
54
|
+
assert.equal(same.ok, true);
|
|
55
|
+
|
|
56
|
+
const drift = assertSameTab(
|
|
57
|
+
"https://jobs.ashbyhq.com/acme/job/123",
|
|
58
|
+
"https://jobs.ashbyhq.com/acme",
|
|
59
|
+
);
|
|
60
|
+
assert.equal(drift.ok, false);
|
|
61
|
+
assert.equal(drift.reason, "tab_drift");
|
|
62
|
+
|
|
63
|
+
const hintFail = assertSameTab(
|
|
64
|
+
"https://jobs.ashbyhq.com/acme/job/123",
|
|
65
|
+
"https://jobs.ashbyhq.com/acme/job/123",
|
|
66
|
+
{ urlContains: "/application" },
|
|
67
|
+
);
|
|
68
|
+
assert.equal(hintFail.ok, false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("write/read session binding stays local under state dir", async (t) => {
|
|
72
|
+
const dir = await mkdtemp(join(tmpdir(), "jaa-binding-"));
|
|
73
|
+
t.after(() => rm(dir, { recursive: true, force: true }));
|
|
74
|
+
|
|
75
|
+
const binding = createSessionBinding({
|
|
76
|
+
attentionId: "attention-abc",
|
|
77
|
+
jobUrl: "https://jobs.ashbyhq.com/acme/application",
|
|
78
|
+
browserProfilePath: "/home/runner/.jaa-chrome-fill",
|
|
79
|
+
applicationId: "app-1",
|
|
80
|
+
roundId: "round-1",
|
|
81
|
+
tabHint: { urlContains: "/application" },
|
|
82
|
+
});
|
|
83
|
+
const path = sessionBindingPath(dir, binding.attentionId);
|
|
84
|
+
await writeSessionBindingFile(path, binding);
|
|
85
|
+
const loaded = await readSessionBindingFile(path);
|
|
86
|
+
assert.equal(loaded.attentionId, "attention-abc");
|
|
87
|
+
assert.equal(loaded.vncPort, 5900);
|
|
88
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
89
|
+
assert.equal(raw.browserProfilePath, "/home/runner/.jaa-chrome-fill");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("extractSessionBindingFields pulls local-only keys", () => {
|
|
93
|
+
const fields = extractSessionBindingFields({
|
|
94
|
+
url: "https://example.com",
|
|
95
|
+
browserProfilePath: "/tmp/p",
|
|
96
|
+
display: ":99",
|
|
97
|
+
vncPort: 5900,
|
|
98
|
+
});
|
|
99
|
+
assert.equal(fields.browserProfilePath, "/tmp/p");
|
|
100
|
+
assert.equal(fields.display, ":99");
|
|
101
|
+
assert.equal(extractSessionBindingFields({ url: "https://example.com" }), null);
|
|
102
|
+
});
|
|
@@ -19,14 +19,39 @@ test('documents durable autonomy, resumable rounds, attention and friction contr
|
|
|
19
19
|
const skill = await readFile(new URL('../SKILL.md', import.meta.url), 'utf8');
|
|
20
20
|
const autonomy = await readFile(new URL('../references/AUTONOMY.md', import.meta.url), 'utf8');
|
|
21
21
|
const runs = await readFile(new URL('../references/RUNS.md', import.meta.url), 'utf8');
|
|
22
|
+
const agentBox = await readFile(new URL('../references/agent-box/README.md', import.meta.url), 'utf8');
|
|
22
23
|
|
|
23
24
|
assert.match(skill, /autonomy status/);
|
|
24
25
|
assert.match(skill, /round status/);
|
|
25
26
|
assert.match(skill, /attention list/);
|
|
26
27
|
assert.match(skill, /friction record/);
|
|
28
|
+
assert.match(skill, /attention-runner-poll/);
|
|
29
|
+
assert.match(skill, /attention-resume-submit/);
|
|
30
|
+
assert.match(skill, /session-binding/);
|
|
31
|
+
assert.match(skill, /DISPLAY=:99/);
|
|
32
|
+
assert.match(skill, /5900/);
|
|
27
33
|
assert.match(autonomy, /never.*merge.*publish/i);
|
|
28
34
|
assert.match(autonomy, /CAPTCHA/i);
|
|
29
35
|
assert.match(runs, /visible.*confirmation/i);
|
|
30
36
|
assert.match(runs, /discoverySource/);
|
|
31
37
|
assert.match(runs, /applicationChannel/);
|
|
38
|
+
assert.match(runs, /Resume → submit/);
|
|
39
|
+
assert.match(runs, /attention-runner-poll/);
|
|
40
|
+
assert.match(runs, /submit if possible/i);
|
|
41
|
+
assert.match(agentBox, /localhost:5900/);
|
|
42
|
+
assert.match(agentBox, /5901/);
|
|
43
|
+
assert.match(agentBox, /DISPLAY=:99/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('documents Free.ai as optional LLM assist only', async () => {
|
|
47
|
+
const skill = await readFile(new URL('../SKILL.md', import.meta.url), 'utf8');
|
|
48
|
+
const freeAi = await readFile(new URL('../references/FREE_AI.md', import.meta.url), 'utf8');
|
|
49
|
+
|
|
50
|
+
assert.match(skill, /FREE_AI\.md/);
|
|
51
|
+
assert.match(skill, /does not call Free\.ai/i);
|
|
52
|
+
assert.match(freeAi, /https:\/\/api\.free\.ai\/v1/);
|
|
53
|
+
assert.match(freeAi, /FREE_AI_API_KEY/);
|
|
54
|
+
assert.match(freeAi, /qwen7b/);
|
|
55
|
+
assert.match(freeAi, /not the hosted/i);
|
|
56
|
+
assert.match(freeAi, /does not read `FREE_AI_API_KEY`/i);
|
|
32
57
|
});
|
|
@@ -739,7 +739,18 @@ test('replays and prioritizes an owner-only attention queue', async (t) => {
|
|
|
739
739
|
stage: 'submission',
|
|
740
740
|
blocker: 'captcha',
|
|
741
741
|
requiredActions: ['complete-captcha'],
|
|
742
|
-
|
|
742
|
+
browserProfilePath: join(directory, 'chrome-fill'),
|
|
743
|
+
display: ':99',
|
|
744
|
+
vncPort: 5900,
|
|
745
|
+
tabHint: { urlContains: '/captcha' },
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
assert.ok(captcha.sessionBinding?.binding?.browserProfilePath);
|
|
749
|
+
assert.equal(captcha.sessionBinding.binding.vncPort, 5900);
|
|
750
|
+
assert.equal(captcha.sessionBinding.binding.display, ':99');
|
|
751
|
+
const attentionRaw = await readFile(join(directory, 'attention.ndjson'), 'utf8');
|
|
752
|
+
assert.doesNotMatch(attentionRaw, /browserProfilePath/);
|
|
753
|
+
assert.ok((await readFile(captcha.sessionBinding.path, 'utf8')).includes('chrome-fill'));
|
|
743
754
|
|
|
744
755
|
const before = cli(env, ['attention', 'list']);
|
|
745
756
|
assert.deepEqual(before.items.map((item) => item.id), [captcha.id, judgment.id]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-application-agent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "A privacy-first Agent Skill and CLI for evidence-based job discovery, application completion, and outcome tracking.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"test": "node --test",
|
|
44
44
|
"check:native-artifacts": "node scripts/ci/validate-native-artifacts.mjs",
|
|
45
|
-
"privacy-audit": "node --test job-application-agent/tests/privacy-audit.test.mjs",
|
|
45
|
+
"privacy-audit": "node --test job-application-agent/tests/privacy-audit.test.mjs job-application-agent/tests/outreach-cli.test.mjs",
|
|
46
46
|
"smoke:package": "node scripts/smoke-package.mjs",
|
|
47
|
-
"check": "node --check job-application-agent/scripts/job-application.mjs && node --check job-application-agent/scripts/cloud-state-client.mjs && node --check job-application-agent/scripts/secret-store.mjs && node --check job-application-agent/scripts/source-community-client.mjs && node --check job-application-agent/scripts/source-community-schema.mjs && node --check job-application-agent/scripts/telemetry-client.mjs && node --check job-application-agent/scripts/telemetry-schema.mjs && node --check job-application-agent/scripts/version.mjs && node --check telemetry-worker/src/worker.mjs && node --check telemetry-worker/src/public-stats.mjs && node --check telemetry-worker/public/dashboard.js && node --check installer/src/cli.mjs && node --check installer/src/installer.mjs && node --check installer/src/runner.mjs && node --check installer/src/scheduler.mjs && node --check scripts/ci/classify-paths.mjs && node --check scripts/ci/validate-native-artifacts.mjs && node --check bin/job-application-agent.mjs && node --check state-worker/src/worker.mjs && node --check state-worker/src/backup.mjs && node --check state-worker/src/cli.mjs && node --check state-worker/bin/sync.mjs && node --check job-application-agent/scripts/application-accounting.mjs",
|
|
47
|
+
"check": "node --check job-application-agent/scripts/job-application.mjs && node --check job-application-agent/scripts/attention-runner-poll.mjs && node --check job-application-agent/scripts/attention-resume-submit.mjs && node --check job-application-agent/scripts/session-binding.mjs && node --check job-application-agent/scripts/novnc-display-guard.mjs && node --check job-application-agent/scripts/ats/submit-adapters.mjs && node --check job-application-agent/scripts/cloud-state-client.mjs && node --check job-application-agent/scripts/secret-store.mjs && node --check job-application-agent/scripts/source-community-client.mjs && node --check job-application-agent/scripts/source-community-schema.mjs && node --check job-application-agent/scripts/telemetry-client.mjs && node --check job-application-agent/scripts/telemetry-schema.mjs && node --check job-application-agent/scripts/version.mjs && node --check telemetry-worker/src/worker.mjs && node --check telemetry-worker/src/public-stats.mjs && node --check telemetry-worker/public/dashboard.js && node --check installer/src/cli.mjs && node --check installer/src/installer.mjs && node --check installer/src/runner.mjs && node --check installer/src/scheduler.mjs && node --check scripts/ci/classify-paths.mjs && node --check scripts/ci/validate-native-artifacts.mjs && node --check bin/job-application-agent.mjs && node --check state-worker/src/worker.mjs && node --check state-worker/src/backup.mjs && node --check state-worker/src/cli.mjs && node --check state-worker/bin/sync.mjs && node --check job-application-agent/scripts/application-accounting.mjs && node --check job-application-agent/scripts/outreach-domain.mjs && node --check job-application-agent/scripts/outreach-store.mjs && node --check job-application-agent/scripts/outreach-cli.mjs && node --check state-worker/src/outreach.mjs && node --check job-application-agent/scripts/ats/answer-inject.mjs && node --check job-application-agent/scripts/attention-questions.mjs && node --check job-application-agent/scripts/captcha-vendor.mjs",
|
|
48
48
|
"prepack": "npm run check && npm test && npm run privacy-audit"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
@@ -52,5 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"yaml": "2.9.0"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"sql.js": "1.13.0"
|
|
55
58
|
}
|
|
56
59
|
}
|