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,119 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { createRecruitContextLoader, } from '../ehr/operations/recruit.js';
|
|
6
|
+
import { rsFail } from './errors.js';
|
|
7
|
+
const flowMemo = new Map();
|
|
8
|
+
const digitalMenuMemo = new Map();
|
|
9
|
+
function cacheDirFor(baseUrl) {
|
|
10
|
+
const url = new URL(baseUrl);
|
|
11
|
+
const host = url.hostname + (url.port ? `_${url.port}` : '');
|
|
12
|
+
const envRoot = process.env.E10_SKILL_CACHE_DIR;
|
|
13
|
+
if (envRoot)
|
|
14
|
+
return join(envRoot, host);
|
|
15
|
+
const home = process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
16
|
+
return join(home, 'env-cache', 'recruit-ops', host);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 磁盘缓存兼容:脚本版(recruit-stage 技能)播种的缓存里若已带 defaultRecruitFlow /
|
|
20
|
+
* lists.digital_human,直接复用(同环境免请求);CLI 侧则运行时解析一次并 memo。
|
|
21
|
+
*/
|
|
22
|
+
async function readLegacyCacheExtra(baseUrl) {
|
|
23
|
+
try {
|
|
24
|
+
const dir = cacheDirFor(baseUrl);
|
|
25
|
+
if (!existsSync(dir))
|
|
26
|
+
return { defaultRecruitFlow: null, digitalHumanMenu: null };
|
|
27
|
+
const files = (await import('node:fs/promises')).readdir(dir);
|
|
28
|
+
const jsonFiles = (await files).filter((f) => f.endsWith('.json'));
|
|
29
|
+
if (jsonFiles.length !== 1)
|
|
30
|
+
return { defaultRecruitFlow: null, digitalHumanMenu: null };
|
|
31
|
+
const raw = JSON.parse(await readFile(join(dir, jsonFiles[0]), 'utf8'));
|
|
32
|
+
const flow = raw?.defaultRecruitFlow && raw.defaultRecruitFlow.id
|
|
33
|
+
? { id: String(raw.defaultRecruitFlow.id), name: String(raw.defaultRecruitFlow.name || '') }
|
|
34
|
+
: null;
|
|
35
|
+
const dh = raw?.lists?.digital_human?.listId
|
|
36
|
+
? {
|
|
37
|
+
menuName: String(raw.lists.digital_human.menuName || '面试数字人配置'),
|
|
38
|
+
listId: String(raw.lists.digital_human.listId),
|
|
39
|
+
cusMenuId: String(raw.lists.digital_human.cusMenuId || ''),
|
|
40
|
+
}
|
|
41
|
+
: null;
|
|
42
|
+
return { defaultRecruitFlow: flow, digitalHumanMenu: dh };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { defaultRecruitFlow: null, digitalHumanMenu: null };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** 默认招聘流程解析:招聘设置/流程列表中「默认启用」的那条(运行时解析,跨环境不写死)。 */
|
|
49
|
+
async function resolveDefaultRecruitFlow(host, baseUrl, appId) {
|
|
50
|
+
const memoKey = baseUrl;
|
|
51
|
+
if (flowMemo.has(memoKey))
|
|
52
|
+
return flowMemo.get(memoKey) ?? null;
|
|
53
|
+
const legacy = await readLegacyCacheExtra(baseUrl);
|
|
54
|
+
if (legacy.defaultRecruitFlow) {
|
|
55
|
+
flowMemo.set(memoKey, legacy.defaultRecruitFlow);
|
|
56
|
+
return legacy.defaultRecruitFlow;
|
|
57
|
+
}
|
|
58
|
+
let result = null;
|
|
59
|
+
try {
|
|
60
|
+
// 招聘流程配置( hire structures ):优先业务配置通道;接口不可用或为空时保持 null(由摘要标注"未解析到默认流程")
|
|
61
|
+
const r = await host.request({
|
|
62
|
+
path: '/api/recruit/position/recruitFlow/list',
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: { appId, current: 1, pageSize: 50 },
|
|
65
|
+
operation: 'context.defaultRecruitFlow',
|
|
66
|
+
});
|
|
67
|
+
const rows = r?.data?.list || r?.data?.rows || r?.data || [];
|
|
68
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
69
|
+
const enabled = list.find((x) => x && (x.isDefault === true || Number(x.isDefault) === 1 || x.default_flow === '1'))
|
|
70
|
+
|| list.find((x) => x && x.id);
|
|
71
|
+
if (enabled?.id) {
|
|
72
|
+
result = { id: String(enabled.id), name: String(enabled.name || enabled.flow_name || '') };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch { /* 接口形态不符/未开放:保持 null,不阻塞建职位 */ }
|
|
76
|
+
flowMemo.set(memoKey, result);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
async function resolveDigitalHumanMenu(host, baseUrl, cache) {
|
|
80
|
+
const memoKey = baseUrl;
|
|
81
|
+
if (digitalMenuMemo.has(memoKey))
|
|
82
|
+
return digitalMenuMemo.get(memoKey) ?? null;
|
|
83
|
+
const legacy = await readLegacyCacheExtra(baseUrl);
|
|
84
|
+
if (legacy.digitalHumanMenu) {
|
|
85
|
+
digitalMenuMemo.set(memoKey, legacy.digitalHumanMenu);
|
|
86
|
+
return legacy.digitalHumanMenu;
|
|
87
|
+
}
|
|
88
|
+
// 从 all_search 菜单里按别名找(跨基线菜单名不同:面试数字人配置/数字人配置)
|
|
89
|
+
const allSearch = (cache.lists?.all_search || []);
|
|
90
|
+
const candidates = allSearch.filter((m) => m.listId);
|
|
91
|
+
let hit = (candidates.find((m) => (m.menuName || '').includes('面试数字人'))
|
|
92
|
+
|| candidates.find((m) => (m.menuName || '').includes('数字人配置'))) ?? null;
|
|
93
|
+
digitalMenuMemo.set(memoKey, hit);
|
|
94
|
+
return hit;
|
|
95
|
+
}
|
|
96
|
+
const loaders = new Map();
|
|
97
|
+
export function createRecruitStageContext() {
|
|
98
|
+
return async (host) => {
|
|
99
|
+
const loaderKey = 'shared';
|
|
100
|
+
if (!loaders.has(loaderKey))
|
|
101
|
+
loaders.set(loaderKey, createRecruitContextLoader());
|
|
102
|
+
const cache = await loaders.get(loaderKey).load(host);
|
|
103
|
+
const baseUrl = (await host.getSession()).baseUrl.replace(/\/+$/, '');
|
|
104
|
+
const [defaultRecruitFlow, digitalHumanMenu] = await Promise.all([
|
|
105
|
+
resolveDefaultRecruitFlow(host, baseUrl, cache.appId),
|
|
106
|
+
resolveDigitalHumanMenu(host, baseUrl, cache),
|
|
107
|
+
]);
|
|
108
|
+
return { ...cache, defaultRecruitFlow, digitalHumanMenu };
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** 供 operation 内直接取上下文的辅助(失败统一转 rs 错误)。 */
|
|
112
|
+
export async function loadContext(host) {
|
|
113
|
+
try {
|
|
114
|
+
return await createRecruitStageContext()(host);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
throw rsFail(e?.message || '招聘环境上下文解析失败', { stage: 'context' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { rsValidationError } from './errors.js';
|
|
7
|
+
function stateRoot(ctx) {
|
|
8
|
+
const configured = ctx?.config?.path();
|
|
9
|
+
if (configured)
|
|
10
|
+
return resolve(configured, '..');
|
|
11
|
+
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
12
|
+
}
|
|
13
|
+
async function loadKey(ctx) {
|
|
14
|
+
const keysDir = resolve(stateRoot(ctx), 'keys');
|
|
15
|
+
const filename = resolve(keysDir, 'recruit-stage-continuation.key');
|
|
16
|
+
await mkdir(keysDir, { recursive: true, mode: 0o700 });
|
|
17
|
+
if (existsSync(filename))
|
|
18
|
+
return readFile(filename);
|
|
19
|
+
const key = randomBytes(32);
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
|
|
22
|
+
if (process.platform !== 'win32')
|
|
23
|
+
await chmod(filename, 0o600).catch(() => undefined);
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return readFile(filename);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function encode(value) {
|
|
31
|
+
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
32
|
+
}
|
|
33
|
+
function signature(payload, key) {
|
|
34
|
+
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
35
|
+
}
|
|
36
|
+
export function createRecruitStageContinuationSigner(ctx) {
|
|
37
|
+
return {
|
|
38
|
+
async issue(payload, ttlMs = 10 * 60_000) {
|
|
39
|
+
const issuedAt = Date.now();
|
|
40
|
+
const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
|
|
41
|
+
const sig = signature(body, await loadKey(ctx));
|
|
42
|
+
return `${body}.${sig}`;
|
|
43
|
+
},
|
|
44
|
+
async verify(token, expectedOperation) {
|
|
45
|
+
if (typeof token !== 'string' || !token.includes('.')) {
|
|
46
|
+
throw rsValidationError('continuation_invalid', 'continuation 无效');
|
|
47
|
+
}
|
|
48
|
+
const [body, provided] = token.split('.');
|
|
49
|
+
const expected = signature(body, await loadKey(ctx));
|
|
50
|
+
const valid = provided.length === expected.length
|
|
51
|
+
&& timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
52
|
+
if (!valid)
|
|
53
|
+
throw rsValidationError('continuation_invalid', 'continuation 签名校验失败');
|
|
54
|
+
let payload;
|
|
55
|
+
try {
|
|
56
|
+
payload = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw rsValidationError('continuation_invalid', 'continuation 载荷解析失败');
|
|
60
|
+
}
|
|
61
|
+
if (!payload || typeof payload !== 'object') {
|
|
62
|
+
throw rsValidationError('continuation_invalid', 'continuation 载荷必须是对象');
|
|
63
|
+
}
|
|
64
|
+
if (typeof payload.expiresAt === 'number' && Date.now() > payload.expiresAt) {
|
|
65
|
+
throw rsValidationError('continuation_expired', 'continuation 已过期,请重新 prepare');
|
|
66
|
+
}
|
|
67
|
+
if (payload.operation && String(payload.operation) !== String(expectedOperation)) {
|
|
68
|
+
throw rsValidationError('continuation_operation_mismatch', `continuation 属于操作 ${String(payload.operation)},不能用于 ${expectedOperation}`);
|
|
69
|
+
}
|
|
70
|
+
return payload;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* recruit-stage 模块错误类型。
|
|
4
|
+
* 形态与 ehr 模块一致(type/subtype/details/partialData),
|
|
5
|
+
* 前缀 rs 避免与 ehr 的错误类型混淆。
|
|
6
|
+
*/
|
|
7
|
+
export class RecruitStageCliError extends CliError {
|
|
8
|
+
type;
|
|
9
|
+
subtype;
|
|
10
|
+
code;
|
|
11
|
+
retryable;
|
|
12
|
+
stage;
|
|
13
|
+
details;
|
|
14
|
+
partialData;
|
|
15
|
+
constructor(type, subtype, message, options = {}) {
|
|
16
|
+
super(message, options.exitCode ?? ExitCode.InternalError);
|
|
17
|
+
this.name = 'RecruitStageCliError';
|
|
18
|
+
this.type = type;
|
|
19
|
+
this.subtype = subtype;
|
|
20
|
+
this.code = options.code;
|
|
21
|
+
this.retryable = options.retryable ?? false;
|
|
22
|
+
this.stage = options.stage;
|
|
23
|
+
this.details = options.details;
|
|
24
|
+
this.partialData = options.partialData;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function rsValidationError(subtype, message, details) {
|
|
28
|
+
return new RecruitStageCliError('validation', subtype, message, {
|
|
29
|
+
details,
|
|
30
|
+
exitCode: ExitCode.UsageError,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function rsAuthError(subtype, message, details) {
|
|
34
|
+
return new RecruitStageCliError('authentication', subtype, message, {
|
|
35
|
+
details,
|
|
36
|
+
exitCode: ExitCode.AuthError,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function rsNetworkError(subtype, message, options = {}) {
|
|
40
|
+
return new RecruitStageCliError('network', subtype, message, {
|
|
41
|
+
...options,
|
|
42
|
+
retryable: options.retryable ?? true,
|
|
43
|
+
exitCode: ExitCode.NetworkError,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function rsConfirmationError(action, details) {
|
|
47
|
+
return new RecruitStageCliError('confirmation', 'required', '该操作需要明确确认', {
|
|
48
|
+
details: { action, ...details },
|
|
49
|
+
exitCode: ExitCode.ConfirmationRequired,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** 业务失败(对应脚本版 throw new Error(...) 的主通道)。 */
|
|
53
|
+
export function rsFail(message, details) {
|
|
54
|
+
return new RecruitStageCliError('api', 'business_error', message, {
|
|
55
|
+
details,
|
|
56
|
+
exitCode: ExitCode.GeneralError,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function rsPartialError(message, partialData, details) {
|
|
60
|
+
return new RecruitStageCliError('partial', 'write_uncertain', message, {
|
|
61
|
+
details,
|
|
62
|
+
partialData,
|
|
63
|
+
exitCode: ExitCode.Partial,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
2
|
+
import { requireE10Session } from '../../internal/e10/context.js';
|
|
3
|
+
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
4
|
+
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
5
|
+
import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
|
|
6
|
+
import { rsAuthError, rsNetworkError, RecruitStageCliError, rsValidationError } from './errors.js';
|
|
7
|
+
function inferProfile(ctx) {
|
|
8
|
+
const options = ctx.getGlobalOptions();
|
|
9
|
+
return options.profile || getActiveProfile();
|
|
10
|
+
}
|
|
11
|
+
export function assertPath(requestPath) {
|
|
12
|
+
if (typeof requestPath !== 'string' || !requestPath.startsWith('/') || requestPath.startsWith('//')) {
|
|
13
|
+
throw rsValidationError('path_invalid', '请求路径必须是相对绝对路径');
|
|
14
|
+
}
|
|
15
|
+
if (/^https?:/i.test(requestPath) || /[\r\n]/.test(requestPath) || requestPath.includes('/../')) {
|
|
16
|
+
throw rsValidationError('path_invalid', '请求路径不允许跨域或路径逃逸');
|
|
17
|
+
}
|
|
18
|
+
if (!requestPath.startsWith('/api/')) {
|
|
19
|
+
throw rsValidationError('path_not_allowed', '只能调用 /api/ 路径');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 与 ehr host 的刻意差异:**不做全局 isBusinessSuccess 强判定**。
|
|
24
|
+
* 原因:EB 表单引擎各接口响应契约不一(getListDatas 返回 {data:{...}} 未必带 status/success/code),
|
|
25
|
+
* 脚本版(write-op/e10-request.mjs)也只在 saveFormData 等个别接口做业务判定——
|
|
26
|
+
* 业务成功判定留给各 operation 按接口语义自行处理,宿主只判 HTTP 状态与会话失效。
|
|
27
|
+
*/
|
|
28
|
+
export function createRecruitStageHost(ctx, dependencies = {}) {
|
|
29
|
+
let e10Session = null;
|
|
30
|
+
let sessionContext = null;
|
|
31
|
+
const requireSession = dependencies.requireSession
|
|
32
|
+
|| ((innerCtx) => requireE10Session(innerCtx, { skipProfileEnrichment: true }));
|
|
33
|
+
async function ensureSession() {
|
|
34
|
+
if (!e10Session) {
|
|
35
|
+
e10Session = await requireSession(ctx);
|
|
36
|
+
sessionContext = {
|
|
37
|
+
baseUrl: e10Session.getBaseUrl(),
|
|
38
|
+
profile: inferProfile(ctx),
|
|
39
|
+
userId: e10Session.userId,
|
|
40
|
+
tenantKey: e10Session.tenantKey,
|
|
41
|
+
eteamsId: e10Session.eteamsId,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return e10Session;
|
|
45
|
+
}
|
|
46
|
+
async function getSession() {
|
|
47
|
+
await ensureSession();
|
|
48
|
+
return sessionContext;
|
|
49
|
+
}
|
|
50
|
+
async function runtime() {
|
|
51
|
+
return createE10RequestRuntime(ctx, await ensureSession(), {
|
|
52
|
+
interceptors: dependencies.interceptors,
|
|
53
|
+
fetch: dependencies.fetch,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function request(request) {
|
|
57
|
+
const { method, path, query, body, operation, multipart, headers: extraHeaders = {}, timeoutMs = 30_000, } = request;
|
|
58
|
+
assertPath(path);
|
|
59
|
+
const resolvedMethod = method || (body === undefined && multipart === undefined ? 'GET' : 'POST');
|
|
60
|
+
let response;
|
|
61
|
+
try {
|
|
62
|
+
response = await (await runtime()).requestJson({
|
|
63
|
+
module: 'recruit-stage',
|
|
64
|
+
operation,
|
|
65
|
+
method: resolvedMethod,
|
|
66
|
+
path,
|
|
67
|
+
query,
|
|
68
|
+
body: multipart === undefined ? body : undefined,
|
|
69
|
+
formData: multipart,
|
|
70
|
+
headers: { 'X-Requested-With': 'XMLHttpRequest', ...extraHeaders },
|
|
71
|
+
timeoutMs,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof E10ApiError && error.type === 'network') {
|
|
76
|
+
throw rsNetworkError(error.subtype, error.message, {
|
|
77
|
+
retryable: error.retryable,
|
|
78
|
+
details: error.details,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
if (isE10SessionExpiredResponse(response)) {
|
|
84
|
+
throw rsAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
85
|
+
}
|
|
86
|
+
if (response.parseError) {
|
|
87
|
+
if (response.status < 200 || response.status >= 300) {
|
|
88
|
+
throw new RecruitStageCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
|
|
89
|
+
code: response.status,
|
|
90
|
+
details: { status: response.status },
|
|
91
|
+
exitCode: 1,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
throw rsNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
95
|
+
}
|
|
96
|
+
if (response.status < 200 || response.status >= 300) {
|
|
97
|
+
throw new RecruitStageCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
|
|
98
|
+
code: response.status,
|
|
99
|
+
details: { status: response.status, message: response.value?.msg },
|
|
100
|
+
exitCode: 1,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// 注意:运行时对响应做了 parseJsonPreservingBigInts(16+ 位整数加引号保精度),
|
|
104
|
+
// 业务代码拿到的 18 位 id 已是字符串形态——脚本版「从原文提取 formDataIds 防丢精度」的
|
|
105
|
+
// 特殊处理在 CLI 侧不再需要。
|
|
106
|
+
return response.value;
|
|
107
|
+
}
|
|
108
|
+
return { request, getSession };
|
|
109
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { ExitCode, isCliError, toErrorEnvelope } from '../../core/errors.js';
|
|
3
|
+
import { createRecruitStageContinuationSigner } from './continuation.js';
|
|
4
|
+
import { createRecruitStageHost } from './host.js';
|
|
5
|
+
import { recruitStageManifest } from './manifest.js';
|
|
6
|
+
import { executeRecruitStageOperation } from './operations/registry.js';
|
|
7
|
+
import { createRecruitStageContext } from './context.js';
|
|
8
|
+
import { rsValidationError } from './errors.js';
|
|
9
|
+
function asOptions(value) {
|
|
10
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
11
|
+
}
|
|
12
|
+
async function readStdin() {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
for await (const chunk of process.stdin) {
|
|
15
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
16
|
+
}
|
|
17
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
18
|
+
}
|
|
19
|
+
/** Windows PowerShell 管道可能写入 UTF-8 BOM,JSON.parse 不接受前导 BOM,先剥离。 */
|
|
20
|
+
function stripBom(value) {
|
|
21
|
+
return value.replace(/^\uFEFF+/, '');
|
|
22
|
+
}
|
|
23
|
+
export async function inputFrom(options, extra = {}) {
|
|
24
|
+
const opts = asOptions(options);
|
|
25
|
+
let input = {};
|
|
26
|
+
if (opts.input && opts.inputJson) {
|
|
27
|
+
throw rsValidationError('input_conflict', '不能同时传 --input 和 --input-json');
|
|
28
|
+
}
|
|
29
|
+
if (opts.inputJson) {
|
|
30
|
+
input = parseJsonInput(String(opts.inputJson), '--input-json');
|
|
31
|
+
}
|
|
32
|
+
if (opts.input) {
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = stripBom(opts.input === '-' ? await readStdin() : await readFile(String(opts.input), 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw rsValidationError('input_unreadable', `无法读取 --input:${String(opts.input)}`);
|
|
39
|
+
}
|
|
40
|
+
input = parseJsonInput(raw, '--input');
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
...input,
|
|
44
|
+
...Object.fromEntries(Object.entries(extra).filter(([, value]) => value !== undefined && value !== '')),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function parseJsonInput(raw, label) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(stripBom(raw));
|
|
50
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
51
|
+
throw new Error('input must be an object');
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw rsValidationError('input_invalid', `${label} 不是合法 JSON 对象`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function successEnvelope(operation, result) {
|
|
60
|
+
return {
|
|
61
|
+
schemaVersion: 1,
|
|
62
|
+
ok: true,
|
|
63
|
+
operation,
|
|
64
|
+
data: result.data === undefined ? null : result.data,
|
|
65
|
+
...(result.meta ? { meta: result.meta } : {}),
|
|
66
|
+
warnings: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function failureEnvelope(error) {
|
|
70
|
+
const structured = error;
|
|
71
|
+
return {
|
|
72
|
+
schemaVersion: 1,
|
|
73
|
+
ok: false,
|
|
74
|
+
...(structured?.partialData === undefined ? {} : { data: structured.partialData }),
|
|
75
|
+
error: toErrorEnvelope(error),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function executeAndPrint(ctx, operation, input) {
|
|
79
|
+
try {
|
|
80
|
+
const host = createRecruitStageHost(ctx);
|
|
81
|
+
const contextLoader = createRecruitStageContext();
|
|
82
|
+
const result = await executeRecruitStageOperation(operation, input, {
|
|
83
|
+
ctx,
|
|
84
|
+
host,
|
|
85
|
+
continuation: createRecruitStageContinuationSigner(ctx),
|
|
86
|
+
recruitContext: () => contextLoader(host),
|
|
87
|
+
});
|
|
88
|
+
ctx.output.write(successEnvelope(operation, result), { json: true });
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error(JSON.stringify(failureEnvelope(error), null, 2));
|
|
92
|
+
process.exitCode = isCliError(error) ? error.exitCode : ExitCode.GeneralError;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export const recruitStageShortcut = {
|
|
96
|
+
name: 'recruit-stage',
|
|
97
|
+
description: '泛微E10招聘助手(JD生成/简历建档/AI面试邀约)CLI',
|
|
98
|
+
category: 'shortcut',
|
|
99
|
+
register({ program, action }) {
|
|
100
|
+
const rs = program.command('recruit-stage').description('泛微E10招聘助手(招聘启事生成/简历解析建档/AI面试邀约与反馈)');
|
|
101
|
+
rs.command('schema')
|
|
102
|
+
.description('Print recruit-stage operation manifest for agents')
|
|
103
|
+
.action(action((ctx) => {
|
|
104
|
+
ctx.output.write(recruitStageManifest, { json: true });
|
|
105
|
+
}));
|
|
106
|
+
rs.command('run')
|
|
107
|
+
.description('Run a recruit-stage operation with JSON input')
|
|
108
|
+
.argument('<operation>', 'operation name, e.g. recruit-stage.jd.create.prepare')
|
|
109
|
+
.option('--input <path>', 'JSON input file, or - for stdin')
|
|
110
|
+
.option('--input-json <json>', 'inline JSON input object')
|
|
111
|
+
.action(action(async (ctx, operation, options) => {
|
|
112
|
+
await executeAndPrint(ctx, String(operation), await inputFrom(options));
|
|
113
|
+
}));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { recruitStageOperationDefinitions } from './operations/registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* 招聘助手(recruit-stage)模块清单。
|
|
4
|
+
* 面向小E「人事管理 → 招聘入职」场景:招聘启事生成 / 简历建档与匹配 / AI 面试邀约与反馈。
|
|
5
|
+
* 产物口径:写操作成功后返回可点击的 Markdown 双链接(详情页 + 列表定位)。
|
|
6
|
+
*/
|
|
7
|
+
function buildOperations() {
|
|
8
|
+
return recruitStageOperationDefinitions().map((definition) => ({
|
|
9
|
+
name: definition.name,
|
|
10
|
+
resource: definition.resource,
|
|
11
|
+
summary: definition.summary,
|
|
12
|
+
...(definition.capability ? { capability: definition.capability } : {}),
|
|
13
|
+
risk: definition.risk,
|
|
14
|
+
...(definition.requiresConfirmation ? { requiresConfirmation: true } : {}),
|
|
15
|
+
inputSchema: definition.inputSchema,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
export const recruitStageManifest = {
|
|
19
|
+
schemaVersion: 1,
|
|
20
|
+
name: 'recruit-stage',
|
|
21
|
+
title: '泛微E10招聘助手(JD生成/简历建档/AI面试邀约)',
|
|
22
|
+
version: '0.1.0',
|
|
23
|
+
host: 'e10-recruit-stage',
|
|
24
|
+
status: 'builtin',
|
|
25
|
+
sourceSkill: 'weaver-e10-recruit-stage',
|
|
26
|
+
capabilities: ['recruit.jd.create', 'recruit.resume.import', 'recruit.interview.arrange', 'recruit.interview.feedback', 'recruit.list', 'recruit.candidate.search'],
|
|
27
|
+
operations: buildOperations(),
|
|
28
|
+
withheldOperations: [],
|
|
29
|
+
};
|