Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ HUAWEI_API_KEY=
AI_PROVIDER=codex
CODEX_PATH=codex
CODEX_HOME=
CODEX_MODEL=gpt-5.6-sol
CODEX_MODEL=gpt-6-astra
CODEX_TIMEOUT_SECONDS=300
AI_REQUEST_TIMEOUT_SECONDS=120
BASE_URL=
Expand Down
221 changes: 183 additions & 38 deletions chrome-extension/background.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion chrome-extension/boss-content.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(function () {
const EXTENSION_VERSION = "2026-09-04-boss-hr-direct";
const EXTENSION_VERSION = "2026-09-06-hr-profile-guard";
const CONTENT_INSTANCE_ID = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
window.__GET_JOBS_BOSS_CONTENT__ = true;
window.__GET_JOBS_BOSS_CONTENT_VERSION__ = EXTENSION_VERSION;
Expand Down
45 changes: 38 additions & 7 deletions chrome-extension/boss-hr-assistant.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
(function () {
"use strict";

if (window.top !== window.self || window.__GET_JOBS_BOSS_HR_ASSISTANT__) return;
window.__GET_JOBS_BOSS_HR_ASSISTANT__ = true;
const PANEL_VERSION = "2026-09-06-hr-all-conversations";
if (window.top !== window.self || window.__GET_JOBS_BOSS_HR_ASSISTANT__ === PANEL_VERSION) return;
window.__GET_JOBS_BOSS_HR_ASSISTANT_CLEANUP__?.();
window.__GET_JOBS_BOSS_HR_ASSISTANT__ = PANEL_VERSION;

const HOST_ID = "getjobs-boss-hr-assistant";
document.getElementById(HOST_ID)?.remove();
const REFRESH_MS = 15_000;
let activeRequest = false;
let latestStatus = null;
let latestProposals = [];
let actionError = "";
let intervalMinutes = 30;

const host = document.createElement("div");
host.id = HOST_ID;
Expand Down Expand Up @@ -43,10 +47,11 @@

render();
refresh();
window.setInterval(() => {
const refreshTimer = window.setInterval(() => {
host.style.display = location.pathname.startsWith("/web/geek/chat") ? "block" : "none";
if (host.style.display !== "none") refresh();
}, REFRESH_MS);
window.__GET_JOBS_BOSS_HR_ASSISTANT_CLEANUP__ = () => { window.clearInterval(refreshTimer); host.remove(); };

async function refresh() {
if (activeRequest || !location.pathname.startsWith("/web/geek/chat")) return;
Expand Down Expand Up @@ -75,21 +80,39 @@
const timing = latestStatus?.lastScanAt ? `|上次扫描 ${formatTime(latestStatus.lastScanAt)}` : "";
const next = latestStatus?.nextScanAt ? `|下次 ${formatTime(latestStatus.nextScanAt)}` : "";
statusBox.textContent = latestStatus
? `${watching ? "值守中:每 60 秒扫描一次" : "值守已停止"}|Chrome 扩展已连接${bridge?.tabBound ? "/当前 BOSS 标签已绑定" : "/标签未绑定"}|Outbox ${bridge?.outboxCount || 0}|NapCat ${latestStatus.napcatConnected ? "已连接" : "未连接"}${timing}${next}${latestStatus.lastError ? `|${latestStatus.lastError}` : ""}`
? `${watching ? (latestStatus.intervalMs === 1800000 ? "值守中:每 30 分钟读取全部会话" : "值守中:每 60 秒检查未读") : "值守已停止"}|Chrome 扩展已连接${bridge?.tabBound ? "/当前 BOSS 标签已绑定" : "/标签未绑定"}|Outbox ${bridge?.outboxCount || 0}|NapCat ${latestStatus.napcatConnected ? "已连接" : "未连接"}${latestStatus.scanRunning ? `|正在逐个读取与生成,已处理 ${latestStatus.scannedCount || 0} 个` : timing + next}${latestStatus.lastError ? `|${latestStatus.lastError}` : ""}`
: "正在连接本地 AI-JobPilot…";
body.appendChild(statusBox);
body.appendChild(element("div", "status", `当前人物档案:${latestStatus?.currentProfileName || "未读取"};切换档案不会切换 BOSS 登录账号。值守期间请先停止再切换。`));
const schedule = document.createElement("select");
schedule.setAttribute("aria-label", "值守检查范围与间隔");
schedule.className = "btn";
for (const [value, label] of [[30, "每 30 分钟:全部会话(含已读)"], [1, "每 1 分钟:仅未读会话"]]) {
const option = element("option", "", label);
option.value = String(value);
schedule.appendChild(option);
}
schedule.value = String(watching ? (latestStatus.intervalMs === 1800000 ? 30 : 1) : intervalMinutes);
schedule.disabled = watching;
schedule.addEventListener("change", () => { intervalMinutes = Number(schedule.value); });
body.appendChild(schedule);

const actions = element("div", "actions");
const start = button("开始值守", "btn primary");
start.disabled = watching;
start.addEventListener("click", () => mutate("hr-start"));
start.disabled = watching || !latestStatus?.currentProfileId || latestStatus?.profileSwitchBlocked;
start.addEventListener("click", () => mutate("hr-start", null, { expectedProfileId: latestStatus?.currentProfileId, intervalMinutes }));
const stop = button("停止", "btn danger");
stop.disabled = !watching;
stop.addEventListener("click", () => mutate("hr-stop"));
const locked = button("全自动(锁定)", "btn locked");
locked.disabled = true;
actions.append(start, stop, locked);
body.appendChild(actions);
const readAll = button("立即读取全部并生成草稿", "btn");
readAll.disabled = !watching || Boolean(latestStatus?.scanRunning);
readAll.addEventListener("click", () => mutate("hr-scan-all", null, { expectedProfileId: latestStatus?.currentProfileId }));
body.appendChild(readAll);
body.appendChild(element("div", "section-title", `待确认回复(${latestProposals.length})`));
if (!latestProposals.length) {
body.appendChild(element("div", "empty", "暂无待确认消息。AI 不会未经确认自动回复。"));
Expand All @@ -107,7 +130,11 @@
element("span", "", proposal.jobName || "未知岗位"),
element("span", "", proposal.hrName || "HR")
);
if (proposal.highValue) meta.appendChild(element("span", "tag", "高价值/需注意"));
const risks = Array.isArray(proposal.riskTags) ? proposal.riskTags : [];
const label = risks.includes("AI_FAILURE") ? "草稿生成失败"
: risks.includes("NON_TEXT_MESSAGE") ? "需人工查看"
: ({ NEEDS_USER: "待补充信息", REJECTION: "婉拒 / 无需回复", NO_REPLY: "无需回复", INTERVIEW_INVITE: "面试邀请", OFFER: "录用意向" })[proposal.classification];
if (label || proposal.highValue) meta.appendChild(element("span", "tag", label || "需注意"));
const source = element("div", "source", `HR:${proposal.sourceMessage || "(非文本消息)"}`);
const draft = document.createElement("textarea");
draft.value = proposal.draft || "";
Expand All @@ -131,7 +158,11 @@
const skip = button("跳过", "btn danger");
skip.addEventListener("click", () => mutate("hr-skip", proposal.id));
cardActions.append(save, send, skip);
card.append(meta, source, draft, cardActions);
card.append(meta, source);
if (proposal.summary) card.appendChild(element("div", "source", proposal.summary));
const missingFacts = Array.isArray(proposal.missingFacts) ? proposal.missingFacts.filter(value => typeof value === "string" && value.trim()) : [];
if (missingFacts.length) card.appendChild(element("div", "source", `需要处理:${missingFacts.join(";")}`));
card.append(draft, cardActions);
return card;
}

Expand Down
76 changes: 50 additions & 26 deletions chrome-extension/boss-hr-bridge.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
(function () {
"use strict";

if (window.top !== window.self || window.__GET_JOBS_BOSS_HR_BRIDGE__) return;
window.__GET_JOBS_BOSS_HR_BRIDGE__ = true;

const CONTENT_VERSION = "2026-09-06-hr-all-conversations";
if (window.top !== window.self || window.__GET_JOBS_BOSS_HR_BRIDGE__ === CONTENT_VERSION) return;
window.__GET_JOBS_BOSS_HR_BRIDGE__ = CONTENT_VERSION;
const support = globalThis.GetJobsBossHrSupport;
const CONTENT_VERSION = "2026-09-04-boss-hr-direct";
const MAX_CAPTURES = 100;
const OPEN_WAIT_MS = 450;

Expand Down Expand Up @@ -35,18 +34,19 @@
}

const initialUnreadTotal = support.unreadTotal(document);
const unread = support.unreadTab(document);
const scanAll = message.scanAll === true;
const unread = scanAll ? support.allTab(document) : support.unreadTab(document);
if (unread && !/active|selected/i.test(String(unread.className || ""))) {
unread.click();
await wait(350);
}

const collected = await collectUnreadTargets(message.deadlineAt);
const collected = await collectUnreadTargets(message.deadlineAt, scanAll);
if (!collected.success) return collected;
const targets = collected.targets;
for (const pending of Array.isArray(message.outbox) ? message.outbox : []) {
if (pending?.uid && !targets.has(pending.uid)) targets.set(pending.uid, pending);
if (targets.size >= MAX_CAPTURES) break;
if (targets.size >= (scanAll ? 1000 : MAX_CAPTURES)) break;
}

// Opening an unread conversation clears its badge. Process the saved targets from
Expand All @@ -59,12 +59,13 @@

const captures = [];
const errors = [];
let scannedCount = 0;
for (const snapshot of targets.values()) {
if (Date.now() > Number(message.deadlineAt || 0)) {
return { success: false, pause: true, errorCode: "BOSS_HR_SCAN_TIMEOUT", message: "单轮扫描超过 5 分钟,已暂停" };
return { success: false, pause: true, errorCode: "BOSS_HR_SCAN_TIMEOUT", message: "本轮读取达到安全时限,已暂停;已保存的进度保留" };
}
const captureId = snapshot.captureId || support.captureId(snapshot);
const stored = await backgroundRequest("BOSS_HR_OUTBOX_PUT", { capture: { ...snapshot, captureId } });
const captureId = snapshot.captureId || (scanAll ? `${message.scanId}:${snapshot.uid}` : support.captureId(snapshot));
const stored = await backgroundRequest("BOSS_HR_OUTBOX_PUT", { capture: { ...snapshot, captureId }, watchSessionId: message.watchSessionId });
if (!stored?.success) return { success: false, pause: true, errorCode: "HR_OUTBOX_WRITE_FAILED", message: "无法在打开会话前保存 Outbox" };

const located = await locateByUid(snapshot.uid);
Expand All @@ -80,24 +81,33 @@
unreadCount: snapshot.unreadCount || visibleSnapshot.unreadCount || 1
};
located.unique.click();
await wait(OPEN_WAIT_MS);
await wait(scanAll ? 2000 : OPEN_WAIT_MS);
const afterSafety = support.pageSafety(document);
if (!afterSafety.safe) return { success: false, pause: true, ...afterSafety };
const session = support.currentSession(document, currentSnapshot);
if (session.uid !== snapshot.uid) {
errors.push({ captureId, errorCode: "BOSS_CHAT_IDENTITY_AMBIGUOUS" });
continue;
}
const messages = support.readMessages(document);
if (!messages.length) {
if (!messages.length && !scanAll) {
errors.push({ captureId, errorCode: "BOSS_CHAT_MESSAGES_MISSING" });
continue;
}
const inbound = support.latestInbound(messages);
if (!inbound) {
if (!inbound && !scanAll) {
errors.push({ captureId, errorCode: "BOSS_CHAT_INBOUND_MISSING" });
continue;
}
session.lastMessage = inbound.text;
session.lastTime = inbound.time || currentSnapshot.lastTime;
session.lastMessage = inbound?.text || "";
session.lastTime = inbound?.time || currentSnapshot.lastTime;
delete session.surfaceText;
captures.push({ captureId, unreadCount: currentSnapshot.unreadCount, session, messages });
const capture = { captureId, unreadCount: currentSnapshot.unreadCount, session, messages };
if (message.streamResults) {
const saved = await backgroundRequest("BOSS_HR_CAPTURE_RESULT", { capture, scanId: message.scanId, watchSessionId: message.watchSessionId });
if (!saved?.success) return { success: false, pause: true, errorCode: saved?.errorCode || "HR_CAPTURE_SUBMIT_FAILED", message: saved?.message || "生成或保存结果未确认,已暂停,不自动重试" };
} else captures.push(capture);
scannedCount++;
}

if (errors.length) {
Expand All @@ -115,38 +125,50 @@
totalUnread: initialUnreadTotal,
captures,
errors,
truncated: targets.size >= MAX_CAPTURES
streamed: Boolean(message.streamResults),
scannedCount,
truncated: targets.size >= (scanAll ? 1000 : MAX_CAPTURES)
};
}

async function collectUnreadTargets(deadlineAt) {
async function collectUnreadTargets(deadlineAt, scanAll = false) {
const targets = new Map();
let unchangedRounds = 0;
let list = null;
let reachedEnd = false;
let list = findScrollableList(support.chatItems(document)[0]);
if (list && list.scrollTop > 0) {
list.scrollTop = 0;
list.dispatchEvent(new Event("scroll", { bubbles: true }));
await wait(350);
}
for (let round = 0; round < 120; round++) {
if (Date.now() > Number(deadlineAt || 0)) {
return { success: false, pause: true, errorCode: "BOSS_HR_SCAN_TIMEOUT", message: "单轮扫描超过 5 分钟,已暂停" };
return { success: false, pause: true, errorCode: "BOSS_HR_SCAN_TIMEOUT", message: "本轮读取达到安全时限,已暂停;已保存的进度保留" };
}
const before = targets.size;
const items = support.chatItems(document);
for (const item of items) {
const snapshot = support.itemSnapshot(item);
if (snapshot.unreadCount <= 0) continue;
if (!scanAll && snapshot.unreadCount <= 0) continue;
if (!snapshot.uid) {
return { success: false, pause: true, errorCode: "BOSS_CHAT_UID_MISSING", message: "未读会话缺少稳定 UID,已暂停避免误认 HR" };
return { success: false, pause: true, errorCode: "BOSS_CHAT_UID_MISSING", message: "会话缺少稳定 UID,已暂停避免误认 HR" };
}
targets.set(snapshot.uid, snapshot);
if (targets.size >= MAX_CAPTURES) break;
if (targets.size >= (scanAll ? 1000 : MAX_CAPTURES)) break;
}
if (targets.size >= (scanAll ? 1000 : MAX_CAPTURES)) {
if (scanAll) return { success: false, pause: true, errorCode: "HR_LIST_LIMIT", message: "会话列表超过本轮安全上限,未完成全部读取,已暂停" };
break;
}
if (targets.size >= MAX_CAPTURES) break;
list ||= findScrollableList(items[0]);
if (!list || list.scrollTop + list.clientHeight >= list.scrollHeight - 2) break;
if (!list || list.scrollTop + list.clientHeight >= list.scrollHeight - 2) { reachedEnd = true; break; }
unchangedRounds = targets.size === before ? unchangedRounds + 1 : 0;
if (unchangedRounds >= 3) break;
list.scrollTop = Math.min(list.scrollHeight, list.scrollTop + Math.max(240, Math.floor(list.clientHeight * 0.85)));
list.dispatchEvent(new Event("scroll", { bubbles: true }));
await wait(140);
}
if (scanAll && !reachedEnd) return { success: false, pause: true, errorCode: "HR_LIST_INCOMPLETE", message: "未能确认已读到会话列表底部,已暂停,未标记全部完成" };
return { success: true, targets };
}

Expand Down Expand Up @@ -205,6 +227,7 @@
const inputs = Array.from(document.querySelectorAll("#chat-input,textarea,[contenteditable='true']"))
.filter((element) => visible(element));
if (inputs.length !== 1) return { success: true, outcome: "FAILED_SAFE", evidence: "聊天输入框未唯一命中" };
if (!Number.isFinite(command.deadlineAt) || Date.now() >= command.deadlineAt) return { success: true, outcome: "FAILED_SAFE", evidence: "发送命令已过期" };
const input = inputs[0];
writeInput(input, command.draft);
if (support.normalizeText(inputValue(input)) !== support.normalizeText(command.draft)) {
Expand All @@ -213,6 +236,7 @@

const sendButtons = Array.from(document.querySelectorAll("button,[role='button']"))
.filter((element) => visible(element) && /^发送$/.test(support.normalizeText(element.textContent)) && !element.disabled);
if (Date.now() >= command.deadlineAt) return { success: true, outcome: "FAILED_SAFE", evidence: "发送命令已过期" };
let dispatched = false;
if (sendButtons.length === 1) {
sendButtons[0].click();
Expand All @@ -238,7 +262,7 @@
const hrName = support.normalizeText(command.hrName);
const companyName = support.normalizeText(command.companyName);
const jobName = support.normalizeText(command.jobName);
return (!hrName || title.includes(hrName))
return session.uid === command.uid && (!hrName || title.includes(hrName))
&& (!companyName || surface.includes(companyName))
&& (!jobName || surface.includes(jobName));
}
Expand Down
Loading
Loading