weaver-work-cli 0.1.39 → 0.1.42
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 +4 -2
- package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
- package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
- package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
- package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
- package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
- package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
- package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
- package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
- package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
- package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
- package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
- package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
- package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
- package/dist/chunks/chunk-BIPYLLVS.js +501 -0
- package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
- package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
- package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
- package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
- package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
- package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
- package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
- package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
- package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
- package/dist/chunks/chunk-NKZRQXST.js +2546 -0
- package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
- package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
- package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
- package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
- package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
- package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
- package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
- package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
- package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
- package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
- package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
- package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
- package/dist/chunks/env-LB7NBL2O.js +19 -0
- package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
- package/dist/chunks/factory-6ZFQXQ46.js +30 -0
- package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
- package/dist/chunks/group-chat-2HHAJN46.js +20 -0
- package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
- package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
- package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
- package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
- package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
- package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
- package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
- package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
- package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
- package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
- package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
- package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
- package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
- package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
- package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
- package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
- package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
- package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
- package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
- package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
- package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
- package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +110 -56
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +28 -1
- package/dist/internal/e10/auth/xiaoe.js +315 -48
- package/dist/internal/e10/auth-recovery.js +36 -5
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +61 -9
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +15 -0
- package/dist/shortcuts/calendar/operations/read.js +31 -22
- package/dist/shortcuts/calendar/operations/write.js +45 -1
- package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
- package/dist/shortcuts/cowork/operations/item-write.js +5 -3
- package/dist/shortcuts/cowork/operations/member-write.js +3 -1
- package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
- package/dist/shortcuts/cowork/operations/shared.js +7 -0
- package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
- package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
- package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
- package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
- package/dist/shortcuts/ehr/operations/recruit.js +10 -4
- package/dist/shortcuts/ehr/operations/shared.js +47 -0
- package/dist/shortcuts/hrm/operations/org.js +118 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/operations/mail-send.js +8 -2
- package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
- package/dist/shortcuts/project/operations/read.js +6 -25
- package/dist/shortcuts/project/operations/shared.js +22 -0
- package/dist/shortcuts/project/operations/write.js +28 -2
- package/dist/shortcuts/recruit-stage/context.js +119 -0
- package/dist/shortcuts/recruit-stage/continuation.js +73 -0
- package/dist/shortcuts/recruit-stage/errors.js +65 -0
- package/dist/shortcuts/recruit-stage/host.js +109 -0
- package/dist/shortcuts/recruit-stage/index.js +115 -0
- package/dist/shortcuts/recruit-stage/manifest.js +29 -0
- package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
- package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
- package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
- package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
- package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
- package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
- package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
- package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
- package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
- package/dist/shortcuts/recruit-stage/shared.js +608 -0
- package/dist/shortcuts/workflow/operations/form.js +21 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
- package/docs/e10-auth.md +22 -1
- package/docs/operation-manual.md +8 -0
- package/package.json +1 -1
- package/scripts/install-skill-to-xiaoe.mjs +105 -0
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +4 -2
- package/skill-template/business-info.json +24 -0
- package/skill-template/domains/recruit-stage.md +29 -0
- package/skills/weaver-e10-calendar/SKILL.md +2 -1
- package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
- package/skills/weaver-e10-cowork/SKILL.md +1 -1
- package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
- package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
- package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
- package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +119 -26
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
- package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
- package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
- package/skills/weaver-e10-shared/SKILL.md +2 -2
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
- package/skills/weaver-e10-shijingran/SKILL.md +3 -2
- package/skills/weaver-e10-shijingran/references/operations.md +3 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
- package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
- package/skills/weaver-e10-workflow/SKILL.md +7 -1
- package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: weaver-e10-recruit-stage
|
|
3
|
+
agent_created: true
|
|
4
|
+
display_name: 泛微E10招聘助手
|
|
5
|
+
display_name_en: Weaver E10 Recruit Assistant Skill
|
|
6
|
+
description: "泛微E10招聘助手:在小e 对话里完成招聘三件事——①招聘启事智能生成:把岗位需求描述转成符合企业标准的招聘启事与岗位JD初稿,确认后自动创建到 E10 招聘岗位列表;②简历解析与人岗匹配:上传简历自动解析建档、触发智能标签,并按岗位筛选推荐优质候选人;③AI面试邀约与记录:查看近期面试安排、安排面试(含AI面试方案选择与通知)、填写面试反馈。字段值、枚举、表单 id 全部运行时解析,跨环境自适应;写操作遵循『先确认摘要、后落库』红线;产物以可点击 Markdown 链接给出。完整接口地址与认证由 weaver-work-cli 托管的登录会话提供。"
|
|
7
|
+
description_zh: "泛微E10招聘助手:在小e 对话里完成招聘三件事——①招聘启事智能生成:把岗位需求描述转成符合企业标准的招聘启事与岗位JD初稿,确认后自动创建到 E10 招聘岗位列表;②简历解析与人岗匹配:上传简历自动解析建档、触发智能标签,并按岗位筛选推荐优质候选人;③AI面试邀约与记录:查看近期面试安排、安排面试(含AI面试方案选择与通知)、填写面试反馈。字段值、枚举、表单 id 全部运行时解析,跨环境自适应;写操作遵循『先确认摘要、后落库』红线;产物以可点击 Markdown 链接给出。完整接口地址与认证由 weaver-work-cli 托管的登录会话提供。"
|
|
8
|
+
description_en: "Weaver E10 Recruit Assistant covers three recruiting flows in the XiaoE chat: (1) job-posting generation - turn spoken requirements into a company-standard posting and JD draft, then auto-create the position after explicit confirmation; (2) resume parsing and person-job matching - parse an uploaded resume, create the talent record with smart tags, and shortlist quality candidates for a position; (3) AI interview scheduling and records - review upcoming interviews, arrange interviews (including AI-interview plan selection and notifications), and submit interview feedback. Field values, enums and form ids are resolved at runtime across environments; write operations follow the confirm-then-commit discipline; deliverables are clickable Markdown links. Address and authentication come from the login session managed by weaver-work-cli."
|
|
9
|
+
category: business
|
|
10
|
+
version: 1.0.18
|
|
11
|
+
author: 泛微网络科技股份有限公司
|
|
12
|
+
permissions: [network]
|
|
13
|
+
execution:
|
|
14
|
+
entry_mode: file
|
|
15
|
+
profile: node-readonly
|
|
16
|
+
requires:
|
|
17
|
+
bins: ["weaver-work-cli"]
|
|
18
|
+
cliHelp: "weaver-work-cli recruit-stage --help"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# 泛微E10招聘助手(recruit-stage)
|
|
22
|
+
|
|
23
|
+
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../weaver-e10-shared/SKILL.md`](../weaver-e10-shared/SKILL.md),其中包含安装、E10 认证、JSON 输出和高风险写入规则。若共享规则不存在、CLI 未安装或读取失败,说明本技能的必需依赖缺失,必须调用宿主提供的提问/确认弹框,向用户说明必须补齐 `weaver-work-cli` 与共享 skill `weaver-e10-shared`(不补齐本技能无法执行)及补齐计划,并把下面两条待执行命令原样完整展示在确认信息中;Agent 不能在未确认时自行安装。用户明确确认后,Agent 必须直接代为按顺序执行且每条仅执行一次,不得要求或引导用户手动执行:① 缺 CLI 时执行 `npm install -g weaver-work-cli`;② 再执行 `weaver-work-cli skills install shared --target-dir <SKILLS_ROOT>` 装回共享 skill,`<SKILLS_ROOT>` 取本 SKILL.md 所在目录的上一级绝对目录(即与本技能同级的 skills 根目录),不要省略 `--target-dir`——省略会装进默认目录,导致本技能依然读不到共享规则。命令均成功后重新读取共享规则;仍读不到就停止并把结果报告用户,不得凭猜测继续。**
|
|
24
|
+
|
|
25
|
+
**共享规则定位与回退(发布包已裁 shared 时必须走此回退,不得直接判定依赖缺失)**:先读 `../weaver-e10-shared/SKILL.md`;该相对路径不存在时,按以下顺序回退定位,命中即读并继续任务,不要中断:① 与本技能目录同级的 `weaver-e10-shared/SKILL.md`(即 skills 根目录下);② weaver-work-cli 安装根下的 `node_modules/weaver-work-cli/skills/weaver-e10-shared/SKILL.md`(安装根可用 `npm root -g` 定位,或从 weaver-work-cli 可执行文件所在目录向上查找 `node_modules`);③ 本机其他已安装位置的 `skills/weaver-e10-shared/SKILL.md`。以上三条均取不到,才判定依赖缺失并进入上述确认流程。
|
|
26
|
+
|
|
27
|
+
> **CLI 形态(v1.0.16 起)**:业务能力全部由 `weaver-work-cli` 的 `recruit-stage` 模块提供(13 个 operation,`weaver-work-cli recruit-stage schema` 可取全量清单),本技能壳只保留编排(agents/openai.yaml)、呈现语义与附件桥脚本。命令形态:
|
|
28
|
+
>
|
|
29
|
+
> ```text
|
|
30
|
+
> weaver-work-cli --json recruit-stage run <operation> --input-json '<JSON>'
|
|
31
|
+
> # 写操作两步协议:prepare 返回 meta.continuation;apply 带 {"confirm":true,"continuation":"<...>"} 才落库
|
|
32
|
+
> ```
|
|
33
|
+
>
|
|
34
|
+
> 命令与参数映射详见 `agents/openai.yaml` 的「命令形态」段。认证由 weaver-work-cli 统一托管(与聚才林技能同构)。
|
|
35
|
+
|
|
36
|
+
> ## 本技能覆盖招聘三件事(v1.0.3)
|
|
37
|
+
>
|
|
38
|
+
> | # | 任务 | 一句话 |
|
|
39
|
+
> | --- | --- | --- |
|
|
40
|
+
> | 1 | 招聘启事智能生成 | 口述岗位需求 → 企业标准招聘启事 + JD 初稿 → 确认后自动建到招聘岗位列表 |
|
|
41
|
+
> | 2 | 简历解析与人岗匹配 | 上传简历自动解析建档、打标签;按岗位筛选推荐优质候选人 |
|
|
42
|
+
> | 3 | AI面试邀约与记录 | 查看近期面试安排 → 安排面试(含 AI 面试方案与通知)→ 面试反馈 |
|
|
43
|
+
>
|
|
44
|
+
> 🔴 **写操作红线:先出确认摘要、用户明确确认后才落库。** 落库动作由 CLI 执行,与页面「保存」等价。
|
|
45
|
+
|
|
46
|
+
## 认证
|
|
47
|
+
|
|
48
|
+
所有请求复用 `weaver-work-cli` 托管的 E10 会话,业务输入只描述业务对象和意图。禁止读取、列出、打印或解析 `.e10-cli` 等认证目录和文件;认证诊断只能通过 `weaver-work-cli auth` 系列命令、`weaver-work-cli doctor --e10` 和业务命令返回的 JSON 错误完成。
|
|
49
|
+
|
|
50
|
+
## 为什么这样做(设计依据)
|
|
51
|
+
|
|
52
|
+
招聘职位/人才/面试计划都是 **EB 表单**(ebuilderform),字段 id、枚举、菜单、appId **每个环境都不同**——所以全部运行时解析,不写死任何环境常量。
|
|
53
|
+
|
|
54
|
+
查看入口用 **Markdown 链接**(平台实证可点击、免登录打开)而不是右栏 iframe:跨站 iframe 拿不到 E10 会话(SameSite,网页端与客户端右栏都会落到扫码页,已实测);也不用 browsertool 卡片(fill_form 在非表单页卡死「正在填单」,实测)。输出契约见 [`references/recruit-output-links.md`](references/recruit-output-links.md)。
|
|
55
|
+
|
|
56
|
+
## Reference 路由表
|
|
57
|
+
|
|
58
|
+
命中任一条件时,执行下一步前读取对应 reference。
|
|
59
|
+
|
|
60
|
+
| 触发条件 | Reference |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| 第 0 步闸门:招聘服务是否安装、取 appId | [`references/recruit-check-install.md`](references/recruit-check-install.md) |
|
|
63
|
+
| 当前环境有哪些能力/菜单/表单(版本差异,运行时探测) | [`references/recruit-capability-check.md`](references/recruit-capability-check.md) |
|
|
64
|
+
| 租户参数解析:objId / formId / listId / appId / 菜单 / 字段 id | [`references/recruit-resolve-metadata.md`](references/recruit-resolve-metadata.md) |
|
|
65
|
+
| 创建人、成员、面试官等人员名称 → EB 用户 ID | [`references/recruit-resolve-creator.md`](references/recruit-resolve-creator.md) |
|
|
66
|
+
| 生成招聘启事/JD 并建职位(写,prepare→apply) | [`references/recruit-jd-create.md`](references/recruit-jd-create.md) |
|
|
67
|
+
| JD 与招聘启事的文案模板、企业口径 | [`references/recruit-jd-template.md`](references/recruit-jd-template.md) |
|
|
68
|
+
| 简历 OCR 解析(仅 AI 无法解析原件时兜底) | [`references/recruit-resume-parse.md`](references/recruit-resume-parse.md) |
|
|
69
|
+
| 简历导入建人才(附件上传、标签、查重) | [`references/recruit-resume-import.md`](references/recruit-resume-import.md) |
|
|
70
|
+
| 按手机号/姓名/邮箱搜索人才,取 talentId | [`references/recruit-search-candidate.md`](references/recruit-search-candidate.md) |
|
|
71
|
+
| 应聘者列表(按职位/批次看候选人台账) | [`references/recruit-list-candidate.md`](references/recruit-list-candidate.md) |
|
|
72
|
+
| 面试管理列表(近 N 天、待反馈) | [`references/recruit-list-interview.md`](references/recruit-list-interview.md) |
|
|
73
|
+
| 安排面试(写,含 AI 面试方案与动作链) | [`references/recruit-arrange-interview.md`](references/recruit-arrange-interview.md) |
|
|
74
|
+
| 提交/修改面试反馈(写,动作链 + 过程日志) | [`references/recruit-edit-interview-feedback.md`](references/recruit-edit-interview-feedback.md) |
|
|
75
|
+
| 催促面试官填写面试评价(会触达真人) | [`references/recruit-urge-feedback.md`](references/recruit-urge-feedback.md) |
|
|
76
|
+
| 应聘过程卡片(辅助,默认不展示) | [`references/recruit-detail-process.md`](references/recruit-detail-process.md) |
|
|
77
|
+
| 按岗位筛选候选人的匹配口径与评分卡 | [`references/recruit-match-candidates.md`](references/recruit-match-candidates.md) |
|
|
78
|
+
| 查看类产物形态:Markdown 双链接(详情页 + 列表定位) | [`references/recruit-output-links.md`](references/recruit-output-links.md) |
|
|
79
|
+
|
|
80
|
+
## 能力范围
|
|
81
|
+
|
|
82
|
+
| 能做 | 说明 |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| 解析岗位需求 | 从自然语言里抽:职位名称 / 职责 / 任职要求 / 招聘人数等;缺项先问 |
|
|
85
|
+
| 生成招聘启事 + JD 初稿 | 按 [`references/recruit-jd-template.md`](references/recruit-jd-template.md)(企业标准模板)生成,全文展示在对话里 |
|
|
86
|
+
| 确认后自动落库 | `jd.create.apply`:`saveFormData`(职位管理)→ `addPosition` 生成职位,与页面「保存」等价 |
|
|
87
|
+
| 打开招聘岗位列表 | 产出 **两个 Markdown 链接**(`viewLink` / `listLocateLink`):① 新建职位详情查看页 ② 列表+滑出新建职位详情 |
|
|
88
|
+
| 简历解析建档 | 上传简历(或本地路径)→ 解析建人才 + 应聘批次 → 触发智能标签 + 平台标签 |
|
|
89
|
+
| 筛选优质候选人 | 按岗位/关键词列应聘者与人才(`recruit-stage.list` / `recruit-stage.candidate.search`),结合标签与匹配口径给出推荐 |
|
|
90
|
+
| 查看近期面试安排 | 面试管理列表按近 N 天过滤、按开始时间排序(`recruit-stage.list`) |
|
|
91
|
+
| 安排面试(含 AI 面试) | 与页面「保存」等价的动作链(保存 → ESB 日程 → jsCode assignedInterview → ESB 提醒);AI 面试时方案(digital_id)必填,未指定则列出候选方案让用户选,**绝不自动取第一条** |
|
|
92
|
+
| 填写面试反馈 | 提交/修改面试反馈(按钮动作智能解析 + addProcessLog 过程日志),回查校验 |
|
|
93
|
+
|
|
94
|
+
| 不做 | 说明 |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| ❌ 跳过确认直接写库 | **必须**先 `prepare` 出摘要、用户确认后才能 `apply` |
|
|
97
|
+
| ❌ 代改已建职位 / 删除数据 | 不在本技能范围 |
|
|
98
|
+
| ❌ 用 browsertool 卡片做**查看**入口 | `fill_form` 在非表单页(详情/列表)卡死「正在填单」(实测);⇒ **查看类一律用 Markdown 链接**(实证可点击) |
|
|
99
|
+
| ❌ 右栏内嵌 E10 页面 | SameSite 限制(已实测),只能链接 + 新 tab |
|
|
100
|
+
| ❌ 替用户跑 AI 面试本身 | AI 面试由 E10 数字人系统执行;本技能负责**安排、通知、台账查看与反馈** |
|
|
101
|
+
|
|
102
|
+
## 固定参数表(**部署期常量,换环境须重新确认**)
|
|
103
|
+
|
|
104
|
+
> 本表是**部署期确认过的常量**(同 `泛微E10考勤请假申请` 的做法)。换租户 / 换环境必须重新确认并更新本表、重新打包。
|
|
105
|
+
|
|
106
|
+
| 参数 | 当前值 | 含义 |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| **目标环境 `E10_BASE_URL`** | **`https://www.e-cology.com.cn`** | 目标 e-cology 地址。小E 云端沙箱的凭证兑换需要它(作为 `domain` 传入);平台未注入时由 AI 按本表在运行命令前设置该环境变量 |
|
|
109
|
+
| 租户 `tenantKey` | `t7akvdnf84`(泛微网络) | 校验用:兑换出的会话 tenantKey 应与它一致(`whoami` 类接口可核) |
|
|
110
|
+
|
|
111
|
+
> CLI 内**不写死**该地址;它只出现在本表与 Agent 指引里,运行时经 `E10_BASE_URL` 环境变量传入。
|
|
112
|
+
|
|
113
|
+
## 前置依赖:登录会话(CLI 统一托管)
|
|
114
|
+
|
|
115
|
+
| 运行端 | 会话来源 | 说明 |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| 小E 客户端 / 云端沙箱 | weaver-work-cli 内置小E 判定与免浏览器续期(`hasXiaoEEnv`:存在任意 `XIAOE_*` 变量即小E;`XIAOE_CLIENT_TYPE=client\|cloud` 路由续期子命令) | 技能不感知,禁止自写环境判定 |
|
|
118
|
+
| WorkBuddy 桌面端 | `weaver-work-cli` 登录态(`weaver-work-cli auth status` 可查) | 未登录时提示用户执行 `weaver-work-cli auth login`,不自动拉浏览器授权 |
|
|
119
|
+
|
|
120
|
+
- 🔴 会话有效性唯一判据是实际请求是否 401;401 → 小E 端自动续期一次 → 仍失败提示重登。
|
|
121
|
+
- **登录失效处置(技能要求 vs CLI 公共层现状)**:本技能要求「小E 客户端/网页版内免人工登录」——401 时由 CLI 自动换票重试(`canAutomaticallyRecoverXiaoE`:环境含 `XIAOE_*` 注入,或历史登录源为 xiaoe-unified,静默恢复不打扰用户)。⚠️ **已知公共层缺陷(2026-11-04 实测)**:换票链中 `tokens.py`(python)排在「纯 Node 读 XiaoE 客户端会话」之前 → 客户端机器无 python 时先报「没设置 PATH」。缺陷位于 `src/internal/e10/auth/xiaoe.ts`(CLI 公共认证层),**由 CLI 维护者修复,本技能不自行改动**。技能侧对策:① 优先在小E 客户端内执行(登录源记住后自动恢复);② 一次性 `weaver-work-cli auth set` 记住登录源;③ 仍失败如实转告并上报公共层,**不引导用户去配 python**。
|
|
122
|
+
- 🔴 **CLI 版本门槛**:技能包 `requires.bins: ["weaver-work-cli"]`;CLI 走 npm 独立发版,**技能包装好 ≠ 能用**,CLI 版本落后会报「不支持操作」。发布顺序:CLI npm 先行 → 技能包后上。
|
|
123
|
+
- 🔴 禁止输出凭证;环境参数(appId/字段/枚举/菜单)由 CLI 五层上下文运行时解析并落盘缓存。
|
|
124
|
+
|
|
125
|
+
## 用户指令路由
|
|
126
|
+
|
|
127
|
+
| 用户说法 | 路由 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| 「帮我生成这个岗位的JD」「创建一个 Java 岗位的招聘职位」「生成招聘启事」 | 进入「招聘启事智能生成」流程(下 §任务1) |
|
|
130
|
+
| 「帮我解析这份简历」「上传简历建档」 + 简历附件/路径 | 进入「简历解析与人岗匹配」流程(下 §任务2 上半) |
|
|
131
|
+
| 「帮我找到适合这个岗位的候选人」「筛选优质候选人」 | 进入「简历解析与人岗匹配」流程(下 §任务2 下半) |
|
|
132
|
+
| 「查看近期的面试安排」 | 进入「AI面试邀约与记录」流程(下 §任务3 步骤 A) |
|
|
133
|
+
| 「我要填哪些面试反馈」「哪些 AI 面试录像好了等我评」 | 跑 `recruit-stage.interview.feedbackTodo` → 直接给 `myAiPendingFeedback`(主)与 `myOtherPendingFeedback`(附带),附面试记录直链 |
|
|
134
|
+
| 「给 XX 安排面试」「安排一场 AI 面试」 | 进入「AI面试邀约与记录」流程(下 §任务3 步骤 B) |
|
|
135
|
+
| 「填写/修改面试反馈」 | 进入「AI面试邀约与记录」流程(下 §任务3 步骤 C) |
|
|
136
|
+
| 「帮我删掉刚才建的职位」 | 本技能不删数据:说明边界并停止 |
|
|
137
|
+
| 「直接建,不用确认了」 | **仍需确认**:写操作红线不可跳过;可请用户确认摘要后立即执行 |
|
|
138
|
+
|
|
139
|
+
## 任务1:招聘启事智能生成
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
[1] 抽取岗位要素(名称 / 职责 / 要求 / 人数) ← 缺项先问
|
|
143
|
+
↓
|
|
144
|
+
[2] 按企业模板生成招聘启事 + JD 初稿,全文展示
|
|
145
|
+
↓
|
|
146
|
+
[3] 跑 recruit-stage.jd.create.prepare(自动补 18 必填默认值 + 检索最近同类职位参考)→ 中文复述确认摘要
|
|
147
|
+
↓
|
|
148
|
+
[4] 用户明确确认
|
|
149
|
+
↓
|
|
150
|
+
[5] 跑 recruit-stage.jd.create.apply(saveFormData → addPosition,写真实数据)
|
|
151
|
+
↓
|
|
152
|
+
[6] 输出:两个 Markdown 链接(详情查看页 + 列表·已定位新建职位)+ JD 初稿全文
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
详细字段说明与命令参数见 [`references/recruit-jd-create.md`](references/recruit-jd-create.md);产物格式见 [`references/recruit-output-links.md`](references/recruit-output-links.md);JD 文案标准见 [`references/recruit-jd-template.md`](references/recruit-jd-template.md)。
|
|
156
|
+
|
|
157
|
+
## 任务2:简历解析与人岗匹配
|
|
158
|
+
|
|
159
|
+
**上半:上传简历 → 解析建档 → 打标签**
|
|
160
|
+
|
|
161
|
+
1. 用户上传简历后**必须拿到原件**(原始简历附件字段必须上传附件):优先附件桥 `weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs --fileid <对话附件原件的fileId>`(凭据自动取自小E 客户端 authToken/public_key,已实测过服务端校验;注意 fileId 是附件原件的,不是产物 id);桥失败 → 桌面端请用户给本地路径;两路都拿不到原件 → 默认不建档并如实说明,仅用户明确同意「先建档后补原件」时才用 `structuredFile` 文本直建(无附件字段)。
|
|
162
|
+
2. **标签策略(默认只打学校标签,其他按需)**:学校归属标签(985/211/双一流 + 校名)由 **CLI 自动推导**——内置教育部名单(随 bundle 内联),从毕业院校自动判定并并入标签候选,**无需拉任何清单**;🔴 其他维度标签(技术栈/年限/城市/方向等)**默认不做**,仅用户明确要求「打标签/匹配标签」时才由 AI 推导 3~6 个候选传入 tags。所有标签按名精确查询(每名 1 次请求、秒级),**不拉全量清单**(该环境平台标签 1924 个,全量拉取曾耗时 ~20s);不存在的进 missing/tagsSkipped,不臆造、不改名凑命中。
|
|
163
|
+
3. **解析由大模型做**(不依赖 OCR):按 [`references/recruit-resume-import.md`](references/recruit-resume-import.md) §13 schema 整理结构化 JSON → `recruit-stage.resume.import.prepare`(带 `structuredFile` 与原件路径)→ 展示**解析结果 + 字段映射 + 查重 + 标签核验**摘要(已存在同手机号人才 → 提示并停住,不重复建档)。OCR(resumeExtraction)**默认不调用**,仅 AI 无法解析原件(如扫描件)时显式开启 OCR 兜底,契约见 [`references/recruit-resume-parse.md`](references/recruit-resume-parse.md)。
|
|
164
|
+
4. 用户确认 → `recruit-stage.resume.import.apply` → 建人才 + 应聘批次 + 智能标签规则匹配(后台规则,未配置则无)+ 平台标签写入(公共/个人按名命中清单)。
|
|
165
|
+
5. **职位推荐(建档时未提职位才做)**:`recruit-stage.list` 拉职位(`--all`、`pageSize` 50)→ 客户端筛「招聘中」→ 按简历要点(技术栈/方向/年限/城市)推荐 ≤5 个,只输出中文名+关键信息,明确「仅推荐、未写入」;列表空/接口失败/信息不足 → 如实说明并请用户指定,**绝不编造职位**。
|
|
166
|
+
6. **转面试**:用户选定职位 → 进任务3-B,**简历池人才(无职位关联)安排面试必须带 `positionId`**(CLI 会明确报错提示,不盲写)。
|
|
167
|
+
7. 产物:**两个 Markdown 链接**(契约见 [`references/recruit-output-links.md`](references/recruit-output-links.md))——① `[查看 <姓名> 的人才卡片](talentCardUrl)`(人才卡片单页)② `[在人才库中打开](talentListLocateUrl)`(人才库列表并滑出该人才详情);缺失哪个就只给另一个。
|
|
168
|
+
|
|
169
|
+
**下半:筛选优质候选人**
|
|
170
|
+
|
|
171
|
+
1. `recruit-stage.list`(按职位找应聘者,取 `candidate` 台账)或 `recruit-stage.candidate.search`(按姓名/手机号/关键词搜人才),契约见 [`references/recruit-list-candidate.md`](references/recruit-list-candidate.md) 与 [`references/recruit-search-candidate.md`](references/recruit-search-candidate.md)。
|
|
172
|
+
2. 结合标签(智能标签 + 平台标签)与 [`references/recruit-match-candidates.md`](references/recruit-match-candidates.md) 的匹配口径(**匹配度=AI 评估、不落库**,不得谎称系统里存了匹配分)汇总推荐;对用户只说中文结论,不展示内部 id。
|
|
173
|
+
|
|
174
|
+
## 任务3:AI面试邀约与记录
|
|
175
|
+
|
|
176
|
+
**A. 查看近期面试安排**:`recruit-stage.list`(`interview` 台账,近 7 天、按开始时间排序)→ 中文汇总(候选人/职位/方式/轮次/面试官/时间/反馈状态),附面试管理列表 Markdown 链接。契约见 [`references/recruit-list-interview.md`](references/recruit-list-interview.md)。
|
|
177
|
+
|
|
178
|
+
**B. 安排面试**:`recruit-stage.interview.arrange.prepare`,按 `talentId` 定位;也支持 **`mobile` / `name`**(需求原句「给手机号XXX应聘者/给XXX安排一个AI面试」直接触发;人才库检索,唯一命中自动采用,多条列出让用户选)。
|
|
179
|
+
- 面试方式含「AI」时:邮箱与 AI 面试方案**必填**;未指定方案 → `prepare` 会列出候选方案让用户选,**绝不自动取第一条**。
|
|
180
|
+
- 也可用 `recruit-stage.interview.arrange.prefill` 输出人才卡片 URL + jsExpr,由 AI 打开卡片走表单联动、HR 核对后保存(见 [`references/recruit-arrange-interview.md`](references/recruit-arrange-interview.md) §11)。
|
|
181
|
+
- 用户确认 → `recruit-stage.interview.arrange.apply`(保存 → ESB 日程 → assignedInterview → ESB 面试提醒,动作链与页面「保存」等价)。
|
|
182
|
+
- 产物:**两个 Markdown 链接**——① `viewLink`(面试安排详情页)② `listLocateLink`(面试管理列表并滑出本次安排)。
|
|
183
|
+
|
|
184
|
+
**C. 面试反馈**:`recruit-stage.interview.feedback.edit.prepare`(带 `talentId` / `result` / `comment`)→ 用户确认 → `recruit-stage.interview.feedback.edit.apply`;CLI 回查反馈与过程日志是否落库。契约见 [`references/recruit-edit-interview-feedback.md`](references/recruit-edit-interview-feedback.md),催促面试官评价见 [`references/recruit-urge-feedback.md`](references/recruit-urge-feedback.md),应聘过程卡片见 [`references/recruit-detail-process.md`](references/recruit-detail-process.md)。面试视频/台账的归集由 E10 AI 面试系统完成,本技能负责**查看台账(A)与反馈(C)**。
|
|
185
|
+
|
|
186
|
+
## 执行纪律(强制)
|
|
187
|
+
|
|
188
|
+
1. **先确认后落库**:任何写操作前必须有用户对确认摘要的明确同意;摘要被修改就重新 `prepare` 再确认。
|
|
189
|
+
2. **不臆造参数**:字段值只能来自用户输入、JD 推导或 CLI 默认值;解析不到就问,不猜。
|
|
190
|
+
3. **不暴露内部标识与执行细节**:对用户只写中文含义,不展示字段 key / optionId / formDataId / appId;**也不展示 CLI 内部执行明细**——ESB 动作名与成功清单、apply 明细、warnings / writeWarnings / 校验明细等一律不进回答,成功即略过,失败且影响结果时才一句人话说明。
|
|
191
|
+
4. **不输出凭证**:不展示 Cookie / ETEAMSID / token,不写入文件。
|
|
192
|
+
5. **产物纪律**:查看类产物用**普通 Markdown 链接**(viewLink / listLocateLink / talentCardUrl 原样搬运);不要包 browsertool 标签(fill_form 在非表单页卡死,实测);**不要用正文 `<artifact>` 标记**(2026-10-16 起平台已剥离正文标记,改走 clientFillResult 通道——CLI 输出里的 artifactMark 字段忽略);url 缺失时只给另一个链接,不输出半截。
|
|
193
|
+
6. **环境自适应**:baseUrl / appId / 字段 id 全部运行时解析;目标环境未部署招聘应用时 CLI 会友好报错,如实转告,禁止换环境硬拼。
|
|
194
|
+
|
|
195
|
+
## 平台兼容
|
|
196
|
+
|
|
197
|
+
命令示例必须同时兼容 Windows 与 macOS/Linux。
|
|
198
|
+
|
|
199
|
+
Windows PowerShell:
|
|
200
|
+
|
|
201
|
+
```powershell
|
|
202
|
+
weaver-work-cli --json recruit-stage run recruit-stage.candidate.search --input-json '{"name":"张三"}'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
macOS/Linux(bash/zsh):
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
weaver-work-cli --json recruit-stage run recruit-stage.candidate.search --input-json '{"name":"张三"}'
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
JSON 一律用单引号包裹、内部双引号不转义;PowerShell 下不要用反斜杠续行、不要用 `echo` 拼 JSON、不要用 `$env:` 之外的 POSIX 写法。
|
|
212
|
+
|
|
213
|
+
## 目录结构
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
weaver-e10-recruit-stage/
|
|
217
|
+
├── SKILL.md ← 本文件(总纲 + 路由 + 纪律)
|
|
218
|
+
├── agents/openai.yaml ← Agent 入口配置(含 CLI 命令映射与编排)
|
|
219
|
+
├── lib/recruit-xiaoe-file.mjs ← ★小E 附件桥(fileId → 本地文件)
|
|
220
|
+
└── references/ ← 17 篇业务文档(命令示例以 agents/openai.yaml 的 CLI 形态为准)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
> 业务实现真源:`weaver-work-cli` 仓 `src/shortcuts/recruit-stage/`(13 个 operation:
|
|
224
|
+
> jd.create ×3、resume.import ×2、interview.arrange ×3、interview.feedback.edit ×2、
|
|
225
|
+
> list、interview.feedbackTodo、candidate.search)。早期脚本版 `scripts/*.mjs` 已停止维护,
|
|
226
|
+
> 保留于 weaver-interface 资料室仅作历史参照;bug 在 CLI 修、抬版本覆盖。
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "泛微E10招聘助手"
|
|
3
|
+
short_description: "招聘三件事:岗位需求转企业标准招聘启事+JD初稿并自动建档到岗位列表;简历解析建档打标签并筛选优质候选人;查看/安排面试(含AI面试)与面试反馈。写操作先确认后落库,产物用可点击 Markdown 链接。"
|
|
4
|
+
default_prompt: |
|
|
5
|
+
使用 $weaver-e10-recruit-stage 处理招聘三件事(按用户说法路由,见下)。**直接路由到对应任务**,不要每次对话先跑自检
|
|
6
|
+
(业务操作自带认证与环境闸门,失败会立刻报出明确原因)。仅当操作报认证/环境类错误(401 / 会话失效 / 未绑定可用的 E10 环境)时,
|
|
7
|
+
才提示用户重新登录(小E 端自动续期 / 桌面端 weaver-work-cli auth login),把报错原文转告并停止。
|
|
8
|
+
|
|
9
|
+
── 命令形态(CLI 模式,v1.0.16 起)───────────────────────────────────────────
|
|
10
|
+
所有业务能力通过 weaver-work-cli 调用(模块 recruit-stage):
|
|
11
|
+
weaver-work-cli recruit-stage run <operation> --input-json '<JSON>'
|
|
12
|
+
operation 清单(weaver-work-cli recruit-stage schema 可取全量):
|
|
13
|
+
recruit-stage.jd.create.prepare / .apply / .prefill (任务1:JD 落库三模式)
|
|
14
|
+
recruit-stage.resume.import.prepare / .apply (任务2:简历建档/更新)
|
|
15
|
+
recruit-stage.interview.arrange.prepare / .apply / .prefill (任务3-B:安排面试)
|
|
16
|
+
recruit-stage.interview.feedback.edit.prepare / .apply (任务3-C:面试反馈)
|
|
17
|
+
recruit-stage.list (6 类列表:demand/position/entry/offer/interview/candidate)
|
|
18
|
+
recruit-stage.interview.feedbackTodo (我的 AI 面试待反馈清单)
|
|
19
|
+
recruit-stage.candidate.search (人才搜索)
|
|
20
|
+
🔴 写操作两步协议:prepare 返回 meta.continuation + 摘要指纹;apply 必须带
|
|
21
|
+
{"confirm":true,"continuation":"<prepare 的 meta.continuation>",...原参数},
|
|
22
|
+
待提交内容与确认摘要不一致会报 fingerprint_mismatch(此时重跑 prepare 再确认)。
|
|
23
|
+
大输出说明:命令输出超过阈值时 stdout 只含截断预览 + refId,取全量用
|
|
24
|
+
weaver-work-cli --input-ref <refId>(或读暂存文件);业务 JSON 很少触发。
|
|
25
|
+
|
|
26
|
+
── 任务1 招聘启事智能生成(触发:生成JD / 生成招聘启事 / 创建招聘职位)─────────────
|
|
27
|
+
1) 抽取岗位要素:职位名称(必缺先问)、职责、任职要求、招聘人数等。信息不足先问,不猜。
|
|
28
|
+
2) 读 references/recruit-jd-template.md(企业标准模板),生成招聘启事 + 岗位 JD 初稿(职责/要求各 4~6 条,贴合用户描述),全文展示在对话里——这是需求的"输出结果"。
|
|
29
|
+
3) 跑 recruit-stage.jd.create.prepare --input-json '{"positionName":"<职位名>","positionDuty":"<职责正文>","workRequest":"<要求正文>","referenceKeyword":"<职位名核心词>","sets":[{"key":"<字段dataKey>","value":"<值>"}]}'
|
|
30
|
+
(sets 可选,AI 依 JD 指定字段值)。操作自动补必填默认值,并返回 recentReferences(按关键词检索的最近同类职位,含薪酬/人数等可读字段)。
|
|
31
|
+
4) 🔴 **提速(少问)**:直接把 recentReferences 里同类职位的常见值当作**默认值**组装进确认摘要
|
|
32
|
+
(招聘人数/薪酬区间/学历/年限/工作地点等全部直接采用参考值,参考记录推导不出的才用系统默认),
|
|
33
|
+
一次性复述摘要请用户确认——用户只需说"确认"或指出要改的字段;**仅职位名称缺失才追问**,其余不问。
|
|
34
|
+
5) 🔴 用户明确确认后才跑 apply(同参数 + confirm:true + continuation)。apply 会写真实数据(saveFormData → addPosition),与页面「保存」等价;禁止跳过确认直接写库。
|
|
35
|
+
6) apply 成功后:把输出里的**两个 Markdown 链接**按顺序放在回答正文里:先 viewLink(查看新建职位详情),再 listLocateLink(打开招聘岗位列表并自动滑出本次新建职位的详情)。某个链接缺失时只给另一个,不输出半截。
|
|
36
|
+
|
|
37
|
+
── 任务2 简历解析与人岗匹配(触发:上传简历/解析简历/建档;找适合岗位的候选人)──────
|
|
38
|
+
上传简历建档:
|
|
39
|
+
1) 🔴 用户上传简历后**必须拿到原件**(原始简历附件字段必须上传附件,不能只建表单):
|
|
40
|
+
① 附件桥下载(技能内唯一保留的脚本):node lib/recruit-xiaoe-file.mjs --fileid <对话附件的fileId> --name "<原始文件名>" → localPath。
|
|
41
|
+
凭据自动取自小E 客户端(authToken/public_key)或平台注入;fileId 必须是**对话附件原件的 fileId**
|
|
42
|
+
(uploadFile 返回的那个),不是产物 id/文档 id(错 id 会回 2001 文件不存在)。
|
|
43
|
+
② 桥失败或拿不到 fileId → 桌面端请用户给简历**本地路径**(resumeFile 参数直传,原件链路同样成立)。
|
|
44
|
+
③ 两路都拿不到原件 → **默认不建档**(附件必须上传),如实说明原因;仅当用户明确同意「先建档、后补原件」时,
|
|
45
|
+
才用文本直建:把对话里已解析的简历信息按 recruit-resume-import.md §13 的 schema 整理为对象传 structured 参数
|
|
46
|
+
(无原件附件,需向用户明示)。**禁止假装解析、禁止编造简历内容**。
|
|
47
|
+
2) 🔴 **两段式提速(先查重后详析)**:
|
|
48
|
+
① 拿到原件后,**先只抽手机号**(从对话已解析内容或原件里快速抽出,不做整份解析),
|
|
49
|
+
跑轻量查重:recruit-stage.candidate.search --input-json '{"mobile":"<手机号>"}'
|
|
50
|
+
② 命中(rows 非空)→ **直接告知「系统已存在该人才」并结束**:给 talentCardUrl 对应链接
|
|
51
|
+
(用返回行的 talentId 拼 /sp/ebdpage/view 卡片页,或直接引用返回信息),不建档不解析,流程到此为止。
|
|
52
|
+
③ 未命中 → 才做完整结构化解析 + prepare(查重/映射/标签核验)→ 确认 → apply。
|
|
53
|
+
(查重入口是 1~2 个请求的轻量读操作,远快于完整 prepare;两段式把"已存在"场景的耗时压到秒级。)
|
|
54
|
+
3) 🔴 标签策略(**默认只打学校标签,其他标签按需**):
|
|
55
|
+
① 学校归属标签由 **CLI 自动推导**(内置教育部名单:985/211/双一流 + 校名,通常 ≤4 个),无需拉任何清单;
|
|
56
|
+
② 🔴 **其他维度标签(技术栈/工作年限/期望城市/岗位方向等)默认不做**——不主动推导、不往 tags 里塞;
|
|
57
|
+
仅当用户明确说「打标签 / 匹配标签 / 看看适合什么标签」时,才从解析结果推导 3~6 个候选放 tags 数组;
|
|
58
|
+
③ 所有标签都按名精确查询(每个名字 1 次请求、秒级),**不再拉全量标签清单**;环境里不存在的会进
|
|
59
|
+
missing / tagsSkipped —— **不要臆造,也不要为了命中而改标签名**;
|
|
60
|
+
④ 推导出的标签在确认摘要里列出并说明依据。
|
|
61
|
+
4) 🔴 解析由**你(大模型)做**,不依赖 OCR:把对话上下文里平台已解析的简历内容(或你自己读原件的结果)
|
|
62
|
+
按 recruit-resume-import.md §13 的 schema 整理,跑
|
|
63
|
+
recruit-stage.resume.import.prepare --input-json '{"structured":{<schema对象>},"resumeFile":"<localPath如有>","tags":["<其他维度标签>"]}'
|
|
64
|
+
(structured 提供解析结果;resumeFile/fileId 同时给出时把**原件**传到附件字段,两者可并存;
|
|
65
|
+
学校标签已自动并入,不用手动传;noSchoolTags:true 可关闭)
|
|
66
|
+
→ 展示「解析结果 + 字段映射 + 查重 + 标签核验 + 学校标签推导」摘要(同手机号人才已存在 → 提示并停住,不重复建档),一次性请用户确认。
|
|
67
|
+
🔴 默认不调 OCR:只有你无法解析原件(如扫描件)时,才显式加 "useOcr":true 兜底(闸门=checkResumeExtraction)。
|
|
68
|
+
5) 🔴 用户确认后跑 apply(同参数 + confirm:true + continuation)→ 建人才 + 应聘批次 + 智能标签匹配 + 平台标签。
|
|
69
|
+
6) 产物:正文放**两个 Markdown 链接**——① [查看 <姓名> 的人才卡片](talentCardUrl) ② [在人才库中打开](talentListLocateUrl)。某个缺失就只给另一个,不输出半截。
|
|
70
|
+
7) 🟢 职位推荐(建档时用户没提职位 → 建完后主动做;提了职位则跳过):
|
|
71
|
+
跑 recruit-stage.list --input-json '{"type":"position","all":true,"pageSize":50}',客户端只筛「招聘中」的,
|
|
72
|
+
再按简历要点(技术栈/岗位方向/工作年限/期望城市)挑选 ≤5 个最匹配的,对用户输出中文名+关键信息,
|
|
73
|
+
明确说明「以上是推荐,未写入任何数据;选定后可直接安排面试」。
|
|
74
|
+
🔴 数据兜底:列表为空 / 接口报错 / 信息不足 → 如实说明,请用户直接说职位名或去页面选择;禁止虚构职位。
|
|
75
|
+
8) 🟢 转面试(用户从推荐中选定职位后:进任务3-B,**简历池人才必须显式带职位**(positionId)。
|
|
76
|
+
筛选优质候选人:
|
|
77
|
+
1) 按职位:recruit-stage.list --input-json '{"type":"candidate","positionId":"<职位id>"}';按关键词:recruit-stage.candidate.search --input-json '{"name":"<关键词>"}'。
|
|
78
|
+
2) 结合标签与 references/recruit-match-candidates.md 的口径汇总推荐(匹配度=AI 评估、不落库);对用户只说中文结论,不展示内部 id。
|
|
79
|
+
|
|
80
|
+
── 任务3 AI面试邀约与记录(触发:查看近期面试安排 / 安排面试 / AI面试 / 面试反馈)────────
|
|
81
|
+
A) 查看近期面试:recruit-stage.list --input-json '{"type":"interview","recent":7,"sort":"start_time"}'
|
|
82
|
+
→ 中文汇总(候选人/职位/方式/轮次/面试官/时间/反馈状态),可附面试管理列表链接。
|
|
83
|
+
B) 安排面试:recruit-stage.interview.arrange.prepare --input-json '{"talentId":"<id>"}' 或 {"mobile":"<手机号>"} 或 {"name":"<姓名>"}(可加 "type":"AI面试","session":"初试","start":"YYYY-MM-DD HH:mm","interviewer":"<姓名>","positionId":"<职位id>")
|
|
84
|
+
🔴 未指定应聘者(姓名/手机号/talentId 都没有)→ **先问「给谁安排」**,拿到后再走流程;不要盲跑 prepare。
|
|
85
|
+
🔴 **提速(能默认就默认,不问面试官)**:
|
|
86
|
+
· 用户说"安排AI面试"→ type 直接传 "AI面试",aiPlan 默认传 **"大模型面试"**(环境有此方案即用;
|
|
87
|
+
无此方案时 CLI 会报错并列出候选,届时再让用户选,绝不臆造方案)。
|
|
88
|
+
· 应聘者已有应聘批次(人才卡片上显示职位关联)→ 直接用现有批次,不问。
|
|
89
|
+
· 简历池/无职位关联人才 → 默认职位=「大模型算法工程师」:用
|
|
90
|
+
recruit-stage.list --input-json '{"type":"position","all":true,"pageSize":50}' 按名检索出该职位的 form_data_id
|
|
91
|
+
作为 positionId 传入(**运行时解析,禁止写死 id**;该 id 各环境不同)。检索不到再问用户。
|
|
92
|
+
· 面试官不问:未指定默认当前登录人(CLI 内置);时间不问:AI 默认当前时间~+7天、其它 +1小时(CLI 内置)。
|
|
93
|
+
· 候选人定位:唯一命中自动采用;多条 → 报错 data.candidates 列给用户选,**绝不自动挑一个**;0 条如实告知。
|
|
94
|
+
🔴 AI 面试时邮箱必填:prepare 报缺邮箱才向用户要邮箱(人才资料已有则不打扰)。
|
|
95
|
+
🔴 用户确认后跑 apply(同参数 + confirm:true + continuation;ESB 日程 → assignedInterview → ESB 提醒,与页面「保存」等价)。
|
|
96
|
+
apply 成功后产物:**两个 Markdown 链接**——① viewLink ② listLocateLink,按顺序原样放回答正文,缺哪个只给另一个。
|
|
97
|
+
🔴 安排的是 **AI 面试**时,apply 成功后**必须追加**一次:recruit-stage.interview.feedbackTodo --input-json '{}'
|
|
98
|
+
→ 取 feedbackTodo.myAiPendingFeedback(count + rows),以一句「您当前还有 N 条待反馈的 AI 面试:」+ 简短列表
|
|
99
|
+
(候选人/职位/轮次/时间)接在两个链接之后;myOtherPendingFeedback 不展示;查询失败不阻塞主流程。
|
|
100
|
+
🔴 输出纪律:对用户只说人话结果——候选人/职位/方式与方案/时间/面试官 + 「候选人会收到邮件面试通知」+ 两个链接;
|
|
101
|
+
**不展示** saveActions/warnings/assigned 等内部字段与内部 id;失败且影响结果时才一句人话说明。
|
|
102
|
+
备选 prefill:recruit-stage.interview.arrange.prefill(输出人才卡片 URL + jsExpr,AI 打开卡片走表单联动、HR 核对后保存)。
|
|
103
|
+
C) 面试反馈:recruit-stage.interview.feedback.edit.prepare --input-json '{"talentId":"<id>","result":"<结论>","comment":"<评语>"}'
|
|
104
|
+
→ 用户确认 → apply(+ confirm + continuation);操作回查反馈与过程日志是否落库。
|
|
105
|
+
面试视频/台账归集由 E10 AI 面试系统完成,本技能负责查看台账与反馈;用户问视频记录时如实说明。
|
|
106
|
+
|
|
107
|
+
── 通用约束 ────────────────────────────────────────────────────────────────
|
|
108
|
+
认证由 weaver-work-cli 统一托管(与聚才林技能同构):会话来自 CLI 登录态(小E 端免浏览器续期 /
|
|
109
|
+
桌面端 weaver-work-cli auth login)。401/会话失效时提示重新登录,**绝不自动拉起浏览器授权**。
|
|
110
|
+
禁止写死域名到任何文件;禁止向用户输出 Cookie/ETEAMSID/token 等凭证;
|
|
111
|
+
字段 id / 枚举 / 菜单 / appId 全部由 CLI 运行时解析(五层上下文 + 缓存),跨环境自适应;
|
|
112
|
+
目标环境未部署招聘应用时操作会友好报错,如实转告。
|
|
113
|
+
🔴 产物只用普通 Markdown 链接(平台实证可点击);不要用 browsertool 卡片(fill_form 在非表单页卡死,实测);
|
|
114
|
+
不要右栏 iframe 内嵌 E10 页面(SameSite,落到扫码页)。
|
|
115
|
+
🔴 写操作红线:先 prepare 出确认摘要、用户明确确认后才 apply;摘要被修改就重跑 prepare 再确认。
|
|
116
|
+
🔴 全局输出纪律(三个任务通用):操作 JSON 的内部执行字段(saveActions / warnings / writeWarnings / subTableVerify /
|
|
117
|
+
tagCheck 细目 / schoolTags.detail 等)**一律不进回答**——用户只关心业务结果;
|
|
118
|
+
成功时输出「做了什么 + 关键业务信息(中文)+ 产物链接」;失败/告警确已影响结果时,用一句人话说明并给下一步建议。
|
|
119
|
+
范围边界:不删数据(删职位/删人才一律拒绝并说明);不代替 E10 数字人执行 AI 面试本身。
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* recruit-xiaoe-file.mjs —— 小E 附件桥(实验性):小E fileId → 下载字节 → 本地文件
|
|
4
|
+
*
|
|
5
|
+
* 背景:用户在小E 对话里上传简历后,平台侧只给 fileId(/rpa/api/upload 服务族),
|
|
6
|
+
* 而 recruit-resume-import.mjs 只认本地路径或 E10 fileid。本脚本把小E fileId 落成本地文件,
|
|
7
|
+
* 之后即可 `recruit-resume-import.mjs --resume-file <本脚本输出的本地路径>` 走既有导入链。
|
|
8
|
+
*
|
|
9
|
+
* 下载契约(与 ecology_xiaoe src/api/file/index.ts fetchFileBlobById 逐条对齐,2026-10-22 实读):
|
|
10
|
+
* GET {xiaoeDomain}/rpa/api/upload/downLoad?id=<fileId>
|
|
11
|
+
* → query 先过 encryptGetParams:原 query + radomkey 打成 JSON → AES-128-ECB/Pkcs7 → base64 = data
|
|
12
|
+
* 本次随机 AES 密钥(16 位大写字母数字)→ RSA/PKCS1v1.5 加密 → base64 = token
|
|
13
|
+
* → 再追加明文 &Authorization=<authToken>(平台口径拼 query,不是请求头)
|
|
14
|
+
* 错误形态:HTTP 200 + JSON {code,msg}(数据安全校验失败 4001 等)——必须识别,不能把错误体存成文件。
|
|
15
|
+
*
|
|
16
|
+
* 凭据(2026-10-22 实测打通):客户端 user_settings.json 自带 **authToken**(网页端同款下载鉴权)与
|
|
17
|
+
* **public_key**(环境 RSA 公钥,密文/明文自适应)——信封+鉴权已过服务端校验(4001→业务码)。
|
|
18
|
+
* 平台注入 XIAOE_USER_TOKEN / 显式 --auth-token / --public-key-b64 仍可覆盖。
|
|
19
|
+
* 注意:--fileid 必须是**用户上传附件时 uploadFile 返回的 fileId**(对话附件原件),
|
|
20
|
+
* 不是产物 id / aidoc 文档 id(传错会回 2001「文件不存在」)。
|
|
21
|
+
*
|
|
22
|
+
* 用法:
|
|
23
|
+
* node recruit-xiaoe-file.mjs --fileid <小E文件id> [--out <保存路径>] [--name <文件名.ext>]
|
|
24
|
+
* [--public-key-b64 <环境公钥>] [--auth-token <显式token>]
|
|
25
|
+
* 输出: JSON { ok, localPath, size, contentType }(失败时 { ok:false, error, hint })
|
|
26
|
+
*/
|
|
27
|
+
import crypto from 'node:crypto';
|
|
28
|
+
import fs from 'node:fs';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import { tmpdir } from 'node:os';
|
|
31
|
+
import { readXiaoeClientSession } from './e10-request.mjs';
|
|
32
|
+
const __T0 = Date.now(); // 耗时基准(P1-3 耗时量化;elapsed_ms 为内部诊断字段,按输出纪律不进回答)
|
|
33
|
+
|
|
34
|
+
// ── 与 ecology_xiaoe src/api/file/encrypt.ts 逐行对齐 ─────────────────────────
|
|
35
|
+
const SIGN_KEY = Buffer.from('X414B2TRNP99O4VV', 'utf8'); // rpa-util 写死的对称密钥(解公钥密文用)
|
|
36
|
+
const FALLBACK_PUBLIC_KEY =
|
|
37
|
+
'Fhv99LE3h3fenoQvEusueULtKqGGPz1wWr9zF+n9LE/z54PPzSuWWU3eyQYKscgBb9YaFo3QZy14eiHJRY6hqrVBdK92q/bOVfOkEihimhzzTgY+QyQRE0oCq31hsmHJ9zWwMxWrmq5iqom0i9q+TUF6bR7tg9CZxhIK/hB5HRBZ+5M8es43VSXUvieoQ8gzyB6QaDQcAgz1LPZtkYX3K6aUl93tpKQom1EAQvvBR8RyQaCXL8iz3CE5aRwFS4KIWMOIfFCgcmXe/rGkWsKcSLsdTHd9N+oVdo8bGsSFrNsxV+aNKsB4aP4SUsG1ylHBd+QNqsYsqLJ6BX+rmcPCLClqE55wyfMfqtyKmtW1G5kZNwExt0v+lRVO1PoxhCcKNf2Xl+0ZGLA6bkidkww4gluOGtWufE/RU70vpyGK+S0T8EqGxg5Wl/moZQn1Mr2fOK97+zvojNOaYbDOi/7MSbBPjTKoxGHQTXzYyw/OkYAApEBSFIyF8gehNlPmRlGNMQersequ9hUjVGr2ixN5jg==';
|
|
38
|
+
|
|
39
|
+
const randomn = (n) => {
|
|
40
|
+
let res = '';
|
|
41
|
+
while (res.length < n) res += Math.random().toString(36).slice(2).toUpperCase();
|
|
42
|
+
return res.slice(0, n);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** AES-128-ECB/Pkcs7 加密 → base64(rpa-util 的 aes) */
|
|
46
|
+
function aesEncryptBase64(plaintext, key) {
|
|
47
|
+
const c = crypto.createCipheriv('aes-128-ecb', key, null);
|
|
48
|
+
c.setAutoPadding(true); // Pkcs7
|
|
49
|
+
return Buffer.concat([c.update(plaintext, 'utf8'), c.final()]).toString('base64');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 用写死对称密钥解开「公钥密文」→ RSA 公钥明文(PEM 或 base64 DER) */
|
|
53
|
+
function decryptPublicKeyBlob(blob) {
|
|
54
|
+
const d = crypto.createDecipheriv('aes-128-ecb', SIGN_KEY, null);
|
|
55
|
+
d.setAutoPadding(true);
|
|
56
|
+
const out = Buffer.concat([d.update(Buffer.from(blob, 'base64')), d.final()]).toString('utf8');
|
|
57
|
+
if (!out || out.length < 32) throw new Error('公钥密文解密结果异常(长度 ' + out.length + ')');
|
|
58
|
+
return out.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 公钥明文 → node 可用的 KeyObject(兼容 PEM / 裸 base64 DER/SPKI) */
|
|
62
|
+
function toPublicKey(material) {
|
|
63
|
+
if (/BEGIN /.test(material)) return crypto.createPublicKey(material);
|
|
64
|
+
const der = Buffer.from(material, 'base64');
|
|
65
|
+
try {
|
|
66
|
+
return crypto.createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
67
|
+
} catch {
|
|
68
|
+
return crypto.createPublicKey({ key: der, format: 'der', type: 'pkcs1' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** rpa-util 的 encryptGetParams:把 query 打成 ?data=<AES>&token=<RSA> 信封 */
|
|
73
|
+
function encryptGetParams(url, publicKeyMaterial) {
|
|
74
|
+
const oldkey = randomn(16);
|
|
75
|
+
const aesKey = Buffer.from(oldkey, 'utf8');
|
|
76
|
+
const pub = toPublicKey(publicKeyMaterial);
|
|
77
|
+
const rawToken = crypto.publicEncrypt({ key: pub, padding: crypto.constants.RSA_PKCS1_PADDING }, aesKey).toString('base64');
|
|
78
|
+
if (!rawToken) return '';
|
|
79
|
+
const token = encodeURIComponent(rawToken);
|
|
80
|
+
const q = url.indexOf('?');
|
|
81
|
+
const query = {};
|
|
82
|
+
const baseUrl = q === -1 ? url : url.slice(0, q);
|
|
83
|
+
if (q !== -1) new URLSearchParams(url.slice(q + 1)).forEach((v, k) => { query[k] = v; });
|
|
84
|
+
query.radomkey = randomn(32);
|
|
85
|
+
return baseUrl + '?data=' + encodeURIComponent(aesEncryptBase64(JSON.stringify(query), aesKey)) + '&token=' + token;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── 主流程 ────────────────────────────────────────────────────────────────────
|
|
89
|
+
function parseArgs(argv) {
|
|
90
|
+
const args = {};
|
|
91
|
+
for (let i = 2; i < argv.length; i++) {
|
|
92
|
+
const a = argv[i];
|
|
93
|
+
if (a.startsWith('--')) {
|
|
94
|
+
const key = a.slice(2);
|
|
95
|
+
const next = argv[i + 1];
|
|
96
|
+
if (next !== undefined && !next.startsWith('--')) { args[key] = next; i++; } else { args[key] = true; }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return args;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function emit(obj) { obj.elapsed_ms = Date.now() - __T0; process.stdout.write(JSON.stringify(obj, null, 2) + '\n'); }
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
const args = parseArgs(process.argv);
|
|
106
|
+
const fileId = String(args.fileid || '').trim();
|
|
107
|
+
if (!fileId) {
|
|
108
|
+
emit({ ok: false, error: '缺少 --fileid <小E文件id>' });
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 会话凭据:优先客户端 user_settings.json 的 **authToken**(网页端同款下载鉴权),
|
|
113
|
+
// 其次平台注入 XIAOE_USER_TOKEN / 显式 --auth-token;domain 平台注入或客户端 erpa。
|
|
114
|
+
let token = String(args['auth-token'] || '').trim();
|
|
115
|
+
let domain = String(process.env.XIAOE_DOMAIN || '').trim();
|
|
116
|
+
let cliPubKey = '';
|
|
117
|
+
const cli = readXiaoeClientSession();
|
|
118
|
+
if (cli) { domain = domain || cli.erpa; cliPubKey = cli.publicKey || ''; if (!token) token = cli.authToken || cli.sessionId; }
|
|
119
|
+
if (!token) token = String(process.env.XIAOE_USER_TOKEN || '').trim();
|
|
120
|
+
if (!token || !domain) {
|
|
121
|
+
emit({ ok: false, error: '缺少小E 凭据(XIAOE_USER_TOKEN / XIAOE_DOMAIN,桌面端需已登录小E 客户端)' });
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
124
|
+
domain = domain.replace(/\/+$/, '');
|
|
125
|
+
|
|
126
|
+
// 公钥:显式 --public-key-b64 > 客户端 public_key(密文/明文自适应)> encrypt.ts 内置兜底
|
|
127
|
+
let pubMaterial;
|
|
128
|
+
const pickPub = (blob) => {
|
|
129
|
+
if (!blob) return '';
|
|
130
|
+
const b = String(blob).trim();
|
|
131
|
+
if (b.includes('BEGIN ') || /^[A-Za-z0-9+/=\r\n]+$/.test(b) && b.length > 100 && !b.startsWith('Fhv')) {
|
|
132
|
+
// 可能已是明文公钥(PEM / SPKI base64)——用一次 RSA 试加密验证可用性
|
|
133
|
+
try { toPublicKey(b); return b; } catch { /* 不是明文,走解密 */ }
|
|
134
|
+
}
|
|
135
|
+
try { return decryptPublicKeyBlob(b); } catch { return ''; }
|
|
136
|
+
};
|
|
137
|
+
try {
|
|
138
|
+
pubMaterial = pickPub(args['public-key-b64'] || cliPubKey) || decryptPublicKeyBlob(FALLBACK_PUBLIC_KEY);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
emit({ ok: false, error: '公钥初始化失败:' + e.message, hint: '用 --public-key-b64 传入本环境公钥(localStorage["publicKey"] 的值)' });
|
|
141
|
+
process.exit(2);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const encUrl = encryptGetParams(domain + '/rpa/api/upload/downLoad?id=' + encodeURIComponent(fileId), pubMaterial);
|
|
145
|
+
if (!encUrl) { emit({ ok: false, error: '下载地址加密失败(公钥不可用)' }); process.exit(2); }
|
|
146
|
+
const url = encUrl + '&Authorization=' + encodeURIComponent(token);
|
|
147
|
+
|
|
148
|
+
let resp;
|
|
149
|
+
try {
|
|
150
|
+
resp = await fetch(url);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
emit({ ok: false, error: '下载请求失败:' + e.message, hint: '确认本机/沙箱可达 ' + domain + '(/rpa/api 服务)' });
|
|
153
|
+
process.exit(2);
|
|
154
|
+
}
|
|
155
|
+
if (!resp.ok) {
|
|
156
|
+
emit({ ok: false, error: '下载失败(HTTP ' + resp.status + ')', hint: 'Authorization 取值可能不对——用 --auth-token 显式传入网页端 localStorage 的 authToken 再试' });
|
|
157
|
+
process.exit(2);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
161
|
+
// HTTP 200 + JSON 错误体识别(口径同 readApiErrorFromBlob:JSON 且带 code/msg 字段)
|
|
162
|
+
const ct = String(resp.headers.get('content-type') || '');
|
|
163
|
+
if (/json/i.test(ct) && buf.length <= 4096) {
|
|
164
|
+
try {
|
|
165
|
+
const j = JSON.parse(buf.toString('utf8'));
|
|
166
|
+
if (j && (j.code !== undefined || j.msg !== undefined)) {
|
|
167
|
+
emit({ ok: false, error: '服务端返回错误:' + String(j.msg || j.message || ('错误码 ' + j.code)), code: j.code });
|
|
168
|
+
process.exit(2);
|
|
169
|
+
}
|
|
170
|
+
} catch { /* 非 JSON,按正常文件 */ }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 文件名:--name > Content-Disposition > 简历默认名
|
|
174
|
+
let name = String(args.name || '').trim();
|
|
175
|
+
if (!name) {
|
|
176
|
+
const cd = String(resp.headers.get('content-disposition') || '');
|
|
177
|
+
const m = cd.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i);
|
|
178
|
+
if (m) { try { name = decodeURIComponent(m[1]); } catch { name = m[1]; } }
|
|
179
|
+
}
|
|
180
|
+
if (!name) name = 'resume-' + fileId + (/\.(pdf|docx?|rtf)$/i.test(ct) ? '' : '.pdf');
|
|
181
|
+
const out = path.resolve(String(args.out || path.join(tmpdir(), 'xiaoe-attach', name)));
|
|
182
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
183
|
+
fs.writeFileSync(out, buf);
|
|
184
|
+
|
|
185
|
+
emit({ ok: true, localPath: out, size: buf.length, contentType: ct, xiaoeFileId: fileId });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
main().catch((e) => { emit({ ok: false, error: e && e.message ? e.message : String(e) }); process.exit(1); });
|