weaver-work-cli 0.1.33 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{archive-FVFY6GZN.js → archive-XBVH7JU7.js} +2 -2
- package/dist/chunks/{archive-oa-NW3OD4UV.js → archive-oa-MZM3EKFQ.js} +2 -2
- package/dist/chunks/{asset-7QJKMIDV.js → asset-4XO7UOUW.js} +2 -2
- package/dist/chunks/{attachment-content-OSAVA6K3.js → attachment-content-VXSWANAC.js} +3 -3
- package/dist/chunks/{blog-I7SDPNW4.js → blog-PGZKOPSC.js} +2 -2
- package/dist/chunks/{caizhilian-O37ZIQNY.js → caizhilian-BH4ZVGUN.js} +2 -2
- package/dist/chunks/{calendar-7KXE4ED3.js → calendar-MXEMTIO4.js} +2 -2
- package/dist/chunks/{chunk-VBCFFFGT.js → chunk-5DQEEGCC.js} +4 -1
- package/dist/chunks/{chunk-KAS45KNR.js → chunk-6LLJFKJE.js} +1 -1
- package/dist/chunks/{chunk-HEL5XTYI.js → chunk-BPE37FPB.js} +26 -4
- package/dist/chunks/{chunk-IHUYP6LO.js → chunk-F746NKNF.js} +1 -1
- package/dist/chunks/{chunk-MQ2I5AA3.js → chunk-HXJQI5NM.js} +2 -2
- package/dist/chunks/{chunk-ILMBNVSG.js → chunk-I6QGIBDM.js} +2 -2
- package/dist/chunks/{chunk-EEWPQE3O.js → chunk-JCSN3JGM.js} +2 -2
- package/dist/chunks/{chunk-D223PTXU.js → chunk-JNG42JKG.js} +29 -1
- package/dist/chunks/{chunk-6ATGWRZM.js → chunk-JUXVYKII.js} +4 -1
- package/dist/chunks/{chunk-GFPXQIY2.js → chunk-JYKABYCV.js} +2 -2
- package/dist/chunks/{chunk-Q6LWB5HX.js → chunk-NCOVUAZK.js} +2 -2
- package/dist/chunks/{chunk-WY74UBJ6.js → chunk-QHFANTX7.js} +900 -204
- package/dist/chunks/{chunk-WRRJ4GG2.js → chunk-QYGO5EE2.js} +2 -2
- package/dist/chunks/{chunk-RBGOMK4Y.js → chunk-T72N74ZT.js} +2 -2
- package/dist/chunks/{chunk-MEEFGWTM.js → chunk-TC6MZAO3.js} +84 -9
- package/dist/chunks/{chunk-OCU4RLHC.js → chunk-VPUTVEMC.js} +98 -34
- package/dist/chunks/{chunk-PRHJGPMA.js → chunk-VPXOPQJI.js} +2 -2
- package/dist/chunks/{chunk-NHA62QD7.js → chunk-WXFPWCXG.js} +3 -3
- package/dist/chunks/{chunk-2FLWXFVS.js → chunk-XMGSYD6S.js} +4 -4
- package/dist/chunks/{conflict-SFNEVJY5.js → conflict-JAB2LJEZ.js} +2 -2
- package/dist/chunks/contract-XI2EEIZU.js +9110 -0
- package/dist/chunks/{cowork-CKWYHXYB.js → cowork-SCPGLBA4.js} +44 -25
- package/dist/chunks/{ebuilder-form-KOLJRVZD.js → ebuilder-form-EYLHNOTA.js} +3 -3
- package/dist/chunks/{ehr-WFOC3B4D.js → ehr-EAP77IMT.js} +172 -9
- package/dist/chunks/{esb-LJZTN663.js → esb-2TGGXEE7.js} +4 -4
- package/dist/chunks/{fna-F7QRIIZC.js → fna-2X7ZE7BU.js} +13 -8
- package/dist/chunks/{host-VQ6JJXGQ.js → host-C6EL2RKL.js} +3 -3
- package/dist/chunks/{host-BCGUVEWX.js → host-I4NRK4MX.js} +3 -3
- package/dist/chunks/{host-DGE2TZD4.js → host-Q4LZGCRU.js} +3 -3
- package/dist/chunks/{host-BQBRHVR3.js → host-UEI5FGDM.js} +3 -3
- package/dist/chunks/{host-KXPTEWFS.js → host-XCLDDXKQ.js} +3 -3
- package/dist/chunks/{hrm-NI6XAKW5.js → hrm-YXERJM76.js} +3 -3
- package/dist/chunks/{im-7TRTF3KT.js → im-A4VZN2TQ.js} +3 -3
- package/dist/chunks/{invoice-AOXWJKYT.js → invoice-2Z6YPMIS.js} +3 -3
- package/dist/chunks/{jiuchuanhui-7NQWEZAW.js → jiuchuanhui-AR5GMKZB.js} +2 -2
- package/dist/chunks/{lazy-dispatch-RNS4EE4S.js → lazy-dispatch-QXW7P4X3.js} +4 -4
- package/dist/chunks/{mail-VDHJAB4O.js → mail-7GZ6CPBJ.js} +44 -23
- package/dist/chunks/{meeting-GA7XCNNP.js → meeting-3NMGA3MB.js} +74 -10
- package/dist/chunks/{okr-TQIRERPQ.js → okr-BZ6XUOAG.js} +2 -2
- package/dist/chunks/{plan-4RY4LN2P.js → plan-DR5CMUYP.js} +2 -2
- package/dist/chunks/{project-D6XT2OC5.js → project-6J63MK33.js} +2 -2
- package/dist/chunks/{registry-JHRIDQ3A.js → registry-KDD7DHNK.js} +4 -4
- package/dist/chunks/{registry-VOGKFFTR.js → registry-TKX2E7C7.js} +2 -2
- package/dist/chunks/{registry-QKMI43Q6.js → registry-VRTNC6UR.js} +8 -8
- package/dist/chunks/{room-recommend-D4EUOOWY.js → room-recommend-XCWRMB5P.js} +4 -4
- package/dist/chunks/{schedule-XPFBHPCV.js → schedule-IVBICP32.js} +4 -4
- package/dist/chunks/{workflow-HLBZRWIY.js → workflow-D74NFK3C.js} +11 -4
- package/dist/index.js +31 -26
- package/dist/internal/e10/fetch-timing.js +31 -0
- package/dist/internal/e10/request-runtime.js +5 -0
- package/dist/shortcuts/cowork/host.js +8 -0
- package/dist/shortcuts/cowork/operations/types.js +17 -2
- package/dist/shortcuts/ehr/operations/recruit-write.js +35 -0
- package/dist/shortcuts/ehr/operations/recruit.js +147 -0
- package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/host.js +8 -0
- package/dist/shortcuts/mail/operations/types.js +17 -2
- package/dist/shortcuts/meeting/index.js +79 -1
- package/dist/shortcuts/meeting/manifest.js +10 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +6 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +1047 -134
- package/dist/shortcuts/meeting/operations/eb/factory.js +101 -7
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +36 -2
- package/dist/shortcuts/meeting/operations/eb-fallback.js +4 -0
- package/dist/shortcuts/meeting/operations/env.js +6 -0
- package/dist/shortcuts/meeting/operations/invitation.js +79 -20
- package/dist/shortcuts/workflow/endpoints.js +8 -3
- package/dist/shortcuts/workflow/operations/form.js +181 -34
- package/dist/shortcuts/workflow/operations.js +23 -1
- package/docs/ehr.md +1 -1
- package/docs/fna.md +2 -1
- package/package.json +1 -1
- package/skill-template/business-info.json +18 -0
- package/skill-template/domains/cowork.md +14 -0
- package/skills/weaver-e10-cowork/SKILL.md +101 -0
- package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
- package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
- package/skills/weaver-e10-jucailin/SKILL.md +21 -3
- package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
- package/skills/weaver-e10-jucailin/references/salary.md +15 -11
- package/skills/weaver-e10-mail/SKILL.md +12 -9
- package/skills/weaver-e10-meeting/SKILL.md +284 -34
- package/skills/weaver-e10-meeting/references/meeting-eb.md +131 -0
- package/skills/weaver-e10-meeting/references/meeting-invitation.md +28 -2
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
- package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
|
@@ -3,6 +3,7 @@ import { requireE10Session } from '../../internal/e10/context.js';
|
|
|
3
3
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
4
4
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
5
5
|
import { CoworkCliError, coworkAuthError, coworkNetworkError, coworkValidationError } from './errors.js';
|
|
6
|
+
import { coworkPerf } from './operations/types.js';
|
|
6
7
|
function inferProfile(ctx) {
|
|
7
8
|
const options = ctx.getGlobalOptions();
|
|
8
9
|
return options.profile || getActiveProfile();
|
|
@@ -97,7 +98,9 @@ export function createCoworkHost(ctx, dependencies = {}) {
|
|
|
97
98
|
|| ((innerCtx) => requireE10Session(innerCtx, { skipProfileEnrichment: true }));
|
|
98
99
|
async function ensureSession() {
|
|
99
100
|
if (!e10Session) {
|
|
101
|
+
const sessionStart = Date.now();
|
|
100
102
|
e10Session = await requireSession(ctx);
|
|
103
|
+
coworkPerf.sessionMs = Date.now() - sessionStart;
|
|
101
104
|
sessionContext = {
|
|
102
105
|
baseUrl: e10Session.getBaseUrl(),
|
|
103
106
|
profile: inferProfile(ctx),
|
|
@@ -123,6 +126,7 @@ export function createCoworkHost(ctx, dependencies = {}) {
|
|
|
123
126
|
assertPath(path);
|
|
124
127
|
const resolvedMethod = method || (body === undefined && form === undefined ? 'GET' : 'POST');
|
|
125
128
|
let response;
|
|
129
|
+
const reqStart = Date.now();
|
|
126
130
|
try {
|
|
127
131
|
response = await (await runtime()).requestJson({
|
|
128
132
|
module: 'cowork',
|
|
@@ -139,6 +143,10 @@ export function createCoworkHost(ctx, dependencies = {}) {
|
|
|
139
143
|
catch (error) {
|
|
140
144
|
mapRuntimeNetworkError(error);
|
|
141
145
|
}
|
|
146
|
+
finally {
|
|
147
|
+
coworkPerf.requestCount += 1;
|
|
148
|
+
coworkPerf.requestMs += Date.now() - reqStart;
|
|
149
|
+
}
|
|
142
150
|
if (response.status === 401 || response.status === 302) {
|
|
143
151
|
throw coworkAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
144
152
|
}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
export const READ = 'read';
|
|
2
2
|
export const READ_BEFORE_WRITE = 'read-before-write';
|
|
3
3
|
export const WRITE = 'write';
|
|
4
|
+
/** 进程内耗时收集器(周军锋 2026-10-20 要求:全链路耗时可量化)。host 写入、coworkSuccess 注出;内部诊断字段不进回答。 */
|
|
5
|
+
export const coworkPerf = {
|
|
6
|
+
t0: Date.now(),
|
|
7
|
+
sessionMs: 0,
|
|
8
|
+
requestCount: 0,
|
|
9
|
+
requestMs: 0,
|
|
10
|
+
};
|
|
4
11
|
export function coworkSuccess(action, data, meta) {
|
|
5
12
|
const payload = { ok: true, action, data };
|
|
6
|
-
|
|
7
|
-
|
|
13
|
+
const perf = {
|
|
14
|
+
elapsed_ms: Date.now() - coworkPerf.t0,
|
|
15
|
+
session_ms: coworkPerf.sessionMs || undefined,
|
|
16
|
+
requests: coworkPerf.requestCount || undefined,
|
|
17
|
+
network_ms: coworkPerf.requestMs || undefined,
|
|
18
|
+
};
|
|
19
|
+
for (const k of Object.keys(perf))
|
|
20
|
+
if (perf[k] === undefined)
|
|
21
|
+
delete perf[k];
|
|
22
|
+
payload.meta = { ...(meta || {}), ...perf };
|
|
8
23
|
return payload;
|
|
9
24
|
}
|
|
10
25
|
/** 构造「对象类型 + 严格禁止额外字段」的 JSON Schema。 */
|
|
@@ -28,6 +28,31 @@ async function requireRecruitCache(env) {
|
|
|
28
28
|
}
|
|
29
29
|
return env.recruitContext.load(env.host);
|
|
30
30
|
}
|
|
31
|
+
// ── 产物链接(2026-10-29 周军锋要求:写操作完成后自动生成链接返回对话框)──
|
|
32
|
+
// URL 模板与 recruit-stage 脚本同源:人才卡片 /sp/ebdpage/view/{ebdDetailPageId}?talentId=(实证);
|
|
33
|
+
// 列表滑出 /sp/ebdfpage/list/{listId}/card/0/{objId}/{dataId}/{listId}?id={dataId}&openSource=field&openSourceId={dataId}&openSourceAppId={appId}(面试管理实证,offer/entry 同构未单独实测);
|
|
34
|
+
// 列表直开 /sp/ebdfpage/list/{listId}[?talentId=](candidate.import 同构)。全部取自环境缓存,id 缺失返回 null 不编造。
|
|
35
|
+
function recruitTalentCardUrl(cache, baseUrl, talentId) {
|
|
36
|
+
if (!cache.ebdDetailPageId || !talentId)
|
|
37
|
+
return null;
|
|
38
|
+
const extra = String(cache.selfUse) === "1" ? "&recruitType=useSelf" : "";
|
|
39
|
+
return `${baseUrl.replace(/\/+$/, "")}/sp/ebdpage/view/${cache.ebdDetailPageId}?talentId=${talentId}${extra}`;
|
|
40
|
+
}
|
|
41
|
+
function recruitSlideLocateUrl(cache, baseUrl, listKey, formKey, dataId) {
|
|
42
|
+
const listId = cache.lists?.[listKey]?.listId;
|
|
43
|
+
const objId = cache.forms?.[formKey]?.objId;
|
|
44
|
+
if (!listId || !objId || !dataId || !cache.appId)
|
|
45
|
+
return null;
|
|
46
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
47
|
+
return `${base}/sp/ebdfpage/list/${listId}/card/0/${objId}/${dataId}/${listId}?id=${dataId}&openSource=field&openSourceId=${dataId}&openSourceAppId=${cache.appId}`;
|
|
48
|
+
}
|
|
49
|
+
function recruitOpenListUrl(cache, baseUrl, listKey, talentId) {
|
|
50
|
+
const menu = cache.lists?.[listKey];
|
|
51
|
+
if (!menu?.listId)
|
|
52
|
+
return null;
|
|
53
|
+
const extra = talentId ? `?talentId=${talentId}${String(cache.selfUse) === "1" ? "&recruitType=useSelf" : ""}` : "";
|
|
54
|
+
return `${baseUrl.replace(/\/+$/, "")}/sp/ebdfpage/list/${menu.listId}${extra}`;
|
|
55
|
+
}
|
|
31
56
|
/** 建前查重:按手机号搜人才库(total 恒 0 平台缺陷,以 data.data[] 非空为准)。 */
|
|
32
57
|
export async function checkDuplicateByMobile(env, cache, mobile) {
|
|
33
58
|
const talent = cache.lists.talent;
|
|
@@ -501,6 +526,8 @@ const candidateCreateApply = {
|
|
|
501
526
|
talentId: newId,
|
|
502
527
|
initTalentBatch: initMsg,
|
|
503
528
|
searchHint: `可用 ehr.recruit.candidate.search 按 name=${name} 或 mobile=${mobile} 回查`,
|
|
529
|
+
talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, newId),
|
|
530
|
+
talentListLocateUrl: recruitOpenListUrl(cache, lease.baseUrl, 'talent', newId),
|
|
504
531
|
});
|
|
505
532
|
},
|
|
506
533
|
};
|
|
@@ -713,6 +740,8 @@ const interviewArrangeApply = {
|
|
|
713
740
|
candidate: `${plan.candidateName}(talentId=${args.talentId})`,
|
|
714
741
|
time: plan.interviewTime,
|
|
715
742
|
interviewer: plan.interviewer,
|
|
743
|
+
talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, args.talentId),
|
|
744
|
+
interviewLocateUrl: recruitSlideLocateUrl(cache, lease.baseUrl, 'interview', 'interview_plan', planFormId),
|
|
716
745
|
});
|
|
717
746
|
},
|
|
718
747
|
};
|
|
@@ -962,6 +991,8 @@ const offerSendApply = {
|
|
|
962
991
|
sendOffer: 'SUCCESS',
|
|
963
992
|
runEsb: { successNum: esbResult.successNum, failNum: esbResult.failNum, msg: esbResult.successMsg },
|
|
964
993
|
nextStep: '回查 offer_status_select 应已变更为「已发送待反馈」',
|
|
994
|
+
talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, payload.talentId),
|
|
995
|
+
offerListUrl: recruitOpenListUrl(cache, lease.baseUrl, 'offer'),
|
|
965
996
|
});
|
|
966
997
|
},
|
|
967
998
|
};
|
|
@@ -1241,6 +1272,7 @@ const entryProcessApply = {
|
|
|
1241
1272
|
saveEstPreEntry: '已落账号',
|
|
1242
1273
|
runEsb: { successNum: esbResult.successNum, failNum: esbResult.failNum, msg: esbResult.successMsg },
|
|
1243
1274
|
nextStep: '回查入职列表 transact_status 应由 1 变为 2',
|
|
1275
|
+
entryListUrl: recruitOpenListUrl(cache, lease.baseUrl, 'entry'),
|
|
1244
1276
|
});
|
|
1245
1277
|
},
|
|
1246
1278
|
};
|
|
@@ -1463,6 +1495,7 @@ const feedbackEditApply = {
|
|
|
1463
1495
|
? { result: backFeedback.result, comment: backFeedback.judgement, changed }
|
|
1464
1496
|
: '回查未取到该反馈',
|
|
1465
1497
|
...(changed ? {} : { warning: '回查未确认变更生效,请人工在 E10 中核对面试反馈,禁止自动重试' }),
|
|
1498
|
+
talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, payload.talentId),
|
|
1466
1499
|
});
|
|
1467
1500
|
},
|
|
1468
1501
|
};
|
|
@@ -1581,6 +1614,7 @@ const feedbackUrgeApply = {
|
|
|
1581
1614
|
requireConfirm(args, 'ehr.recruit.candidate.interview.feedback.urge.apply');
|
|
1582
1615
|
const lease = await verifyLease(args, 'ehr.recruit.candidate.interview.feedback.urge.apply', env);
|
|
1583
1616
|
const payload = (lease.payload || {});
|
|
1617
|
+
const cache = await requireRecruitCache(env);
|
|
1584
1618
|
if (!payload.feedbackRecordId || !payload.esbActionId || !payload.interviewDetailObjId) {
|
|
1585
1619
|
throw ehrValidationError('continuation_payload_invalid', 'continuation 缺少催评参数,请重新执行 prepare');
|
|
1586
1620
|
}
|
|
@@ -1602,6 +1636,7 @@ const feedbackUrgeApply = {
|
|
|
1602
1636
|
status: 'COMPLETE',
|
|
1603
1637
|
feedbackRecordId: payload.feedbackRecordId,
|
|
1604
1638
|
runEsb: { successNum: esbResult.successNum, failNum: esbResult.failNum, msg: esbResult.successMsg },
|
|
1639
|
+
talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, payload.talentId),
|
|
1605
1640
|
});
|
|
1606
1641
|
},
|
|
1607
1642
|
};
|
|
@@ -698,6 +698,152 @@ const candidateList = {
|
|
|
698
698
|
});
|
|
699
699
|
},
|
|
700
700
|
};
|
|
701
|
+
/** 当前登录人姓名(teamsCheck → currentUser.username),用于「我作为面试官」匹配 */
|
|
702
|
+
/** 人员字段可能是 JSON 数组串([{name,id,...}])→ 取姓名连接;纯文本原样返回 */
|
|
703
|
+
function personNames(value) {
|
|
704
|
+
const text = String(value ?? '').trim();
|
|
705
|
+
if (!text)
|
|
706
|
+
return '';
|
|
707
|
+
if (text.startsWith('[')) {
|
|
708
|
+
try {
|
|
709
|
+
const arr = JSON.parse(text);
|
|
710
|
+
if (Array.isArray(arr)) {
|
|
711
|
+
const names = arr
|
|
712
|
+
.map((item) => (typeof item === 'string' ? item : item?.name ?? item?.username ?? ''))
|
|
713
|
+
.filter(Boolean);
|
|
714
|
+
if (names.length)
|
|
715
|
+
return names.join('、');
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
catch { /* 非 JSON,按纯文本处理 */ }
|
|
719
|
+
}
|
|
720
|
+
return text;
|
|
721
|
+
}
|
|
722
|
+
/** 展示名兜底:跨基线 displayData 会把关联字段直接给名字(talent_id=人名、position_manage_id=职位名);纯 16 位以上数字视为 id,不当名字用 */
|
|
723
|
+
function nameLike(value) {
|
|
724
|
+
const text = String(value ?? '').trim();
|
|
725
|
+
if (!text || /^\d{16,}$/.test(text))
|
|
726
|
+
return null;
|
|
727
|
+
return text;
|
|
728
|
+
}
|
|
729
|
+
async function resolveCurrentUserName(host) {
|
|
730
|
+
// teamsCheck 的响应不被 isBusinessSuccess 认可(无 status 字段),故直接 fetch,绕开业务判定
|
|
731
|
+
try {
|
|
732
|
+
const session = await host.getSession();
|
|
733
|
+
const baseUrl = String(session?.baseUrl || '');
|
|
734
|
+
if (!baseUrl)
|
|
735
|
+
return '';
|
|
736
|
+
const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
|
|
737
|
+
method: 'POST',
|
|
738
|
+
headers: {
|
|
739
|
+
accept: 'application/json',
|
|
740
|
+
'content-type': 'application/json',
|
|
741
|
+
eteamsid: String(session?.eteamsId || ''),
|
|
742
|
+
cookie: typeof session?.cookieHeader === 'function' ? session.cookieHeader() : '',
|
|
743
|
+
'user-agent': typeof session?.uaHeader === 'function' ? session.uaHeader() : 'weaver-work-cli',
|
|
744
|
+
origin: baseUrl,
|
|
745
|
+
},
|
|
746
|
+
redirect: 'manual',
|
|
747
|
+
});
|
|
748
|
+
const data = await resp.json().catch(() => ({}));
|
|
749
|
+
const cu = data.currentUser ?? data.data?.currentUser ?? null;
|
|
750
|
+
return cu ? String(cu.username ?? cu.id ?? '') : '';
|
|
751
|
+
}
|
|
752
|
+
catch {
|
|
753
|
+
return '';
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const interviewFeedbackTodo = {
|
|
757
|
+
name: 'ehr.recruit.interview.feedbackTodo',
|
|
758
|
+
resource: 'recruit',
|
|
759
|
+
method: 'interview.feedbackTodo',
|
|
760
|
+
action: 'ehr.recruit.interview.feedbackTodo',
|
|
761
|
+
summary: '面试待办:**只返回我作为面试官的 AI 面试待反馈清单**(候选人已答完、已有录像、我还没填意见);按需翻页 + 客户端筛选,不返回全量面试',
|
|
762
|
+
capability: 'recruit',
|
|
763
|
+
risk: READ,
|
|
764
|
+
inputSchema: schemaOf({
|
|
765
|
+
maxPages: { type: 'integer', minimum: 1, maximum: 50, description: '最多翻页数(默认 5)' },
|
|
766
|
+
pageSize: { type: 'integer', minimum: 1, maximum: 200, description: '单页条数(默认 200,实测服务端上限 200)' },
|
|
767
|
+
}),
|
|
768
|
+
handler: async (args, env) => {
|
|
769
|
+
if (!env.recruitContext)
|
|
770
|
+
throw ehrValidationError('recruit_context_unavailable', '招聘上下文加载器未初始化');
|
|
771
|
+
const cache = await env.recruitContext.load(env.host);
|
|
772
|
+
const listParam = cache.lists.interview;
|
|
773
|
+
const form = cache.forms.interview_plan;
|
|
774
|
+
if (!listParam?.listId || !form?.objId) {
|
|
775
|
+
throw new RecruitContextError('recruit_list_unavailable', '当前环境未启用「面试管理」功能(未找到面试列表/表单参数,按能力探测拦截)。');
|
|
776
|
+
}
|
|
777
|
+
const pageSize = toNumber(args.pageSize, 'pageSize') ?? 200;
|
|
778
|
+
const maxPages = toNumber(args.maxPages, 'maxPages') ?? 5;
|
|
779
|
+
const me = await resolveCurrentUserName(env.host);
|
|
780
|
+
// 枚举可能回退成 optionId:用环境缓存的 field_options 运行时映射回中文(不写死 id)
|
|
781
|
+
const fields = cache.fields[form.objId] || {};
|
|
782
|
+
const fieldOpts = cache.field_options[form.objId] || {};
|
|
783
|
+
const statusOpts = fieldOpts[fields.union_status] || {};
|
|
784
|
+
const typeOpts = fieldOpts[fields.interview_type] || {};
|
|
785
|
+
const toCn = (map, raw) => {
|
|
786
|
+
const text = String(raw ?? '');
|
|
787
|
+
return map[text] ?? text;
|
|
788
|
+
};
|
|
789
|
+
const statusOf = (row) => toCn(statusOpts, row?.union_status_key ?? row?.union_status);
|
|
790
|
+
const typeOf = (row) => toCn(typeOpts, row?.interview_type_key ?? row?.interview_type);
|
|
791
|
+
const interviewerOf = (row) => personNames(row?.interviewer);
|
|
792
|
+
const scanned = [];
|
|
793
|
+
let lastPageCount = 0;
|
|
794
|
+
for (let i = 0; i < maxPages; i++) {
|
|
795
|
+
const body = buildListBody({
|
|
796
|
+
listId: listParam.listId,
|
|
797
|
+
cusMenuId: listParam.cusMenuId,
|
|
798
|
+
menuName: listParam.menuName || '面试管理',
|
|
799
|
+
title: '面试管理',
|
|
800
|
+
current: i + 1,
|
|
801
|
+
pageSize,
|
|
802
|
+
});
|
|
803
|
+
const page = await fetchListRows(env.host, form.objId, listParam.listId, body);
|
|
804
|
+
const use = page.displayRows && page.displayRows.length ? page.displayRows : page.rows;
|
|
805
|
+
scanned.push(...use);
|
|
806
|
+
lastPageCount = use.length;
|
|
807
|
+
if (use.length < pageSize)
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
// 「待反馈」= 后端 InterviewStatusEnum.TO_FEEDBACK:
|
|
811
|
+
// AI 面试结束时间已过 且(有录像附件 或 已开始录制)且 反馈结果为空
|
|
812
|
+
// ⇒ 这个状态本身就等于「已完成 + 有视频 + 未填意见」,无需再读视频字段
|
|
813
|
+
const pending = scanned.filter((row) => /待反馈/.test(statusOf(row)));
|
|
814
|
+
const mineAll = me ? pending.filter((row) => interviewerOf(row).includes(me)) : [];
|
|
815
|
+
const mineAi = mineAll.filter((row) => /AI\s*面试/i.test(typeOf(row)));
|
|
816
|
+
const slim = (row) => ({
|
|
817
|
+
form_data_id: row?.form_data_id ?? row?.id ?? null,
|
|
818
|
+
candidate_name: nameLike(row?.candidate_name) ?? nameLike(row?.talent_id) ?? nameLike(row?.name),
|
|
819
|
+
position_name: nameLike(row?.position_name) ?? nameLike(row?.position_manage_id),
|
|
820
|
+
start_time: row?.start_time ?? null,
|
|
821
|
+
interview_type_key: typeOf(row) || null,
|
|
822
|
+
interview_session: row?.interview_session ?? null,
|
|
823
|
+
interviewer: interviewerOf(row) || null,
|
|
824
|
+
union_status_key: statusOf(row) || null,
|
|
825
|
+
subject: row?.subject ?? null,
|
|
826
|
+
talent_id: row?.talent_id ?? null,
|
|
827
|
+
position_manage_id: row?.position_manage_id ?? null,
|
|
828
|
+
});
|
|
829
|
+
const notes = [];
|
|
830
|
+
if (!me)
|
|
831
|
+
notes.push('未能解析当前登录人姓名(teamsCheck 失败),无法按面试官过滤');
|
|
832
|
+
if (lastPageCount >= pageSize && scanned.length >= pageSize * maxPages) {
|
|
833
|
+
notes.push(`已扫描 ${scanned.length} 条(达 maxPages=${maxPages} 上限),如怀疑遗漏可加大 maxPages`);
|
|
834
|
+
}
|
|
835
|
+
return ehrSuccess('ehr.recruit.interview.feedbackTodo', {
|
|
836
|
+
currentUser: me || null,
|
|
837
|
+
scanned: scanned.length,
|
|
838
|
+
myAiPendingFeedback: { count: mineAi.length, rows: mineAi.map(slim) },
|
|
839
|
+
myOtherPendingFeedback: {
|
|
840
|
+
count: mineAll.length - mineAi.length,
|
|
841
|
+
rows: mineAll.filter((row) => !/AI\s*面试/i.test(typeOf(row))).map(slim),
|
|
842
|
+
},
|
|
843
|
+
...(notes.length ? { note: notes.join(';') } : {}),
|
|
844
|
+
});
|
|
845
|
+
},
|
|
846
|
+
};
|
|
701
847
|
const candidateCard = {
|
|
702
848
|
name: 'ehr.recruit.candidate.card',
|
|
703
849
|
resource: 'recruit',
|
|
@@ -815,6 +961,7 @@ export const recruitOperations = [
|
|
|
815
961
|
candidateSearch,
|
|
816
962
|
candidateList,
|
|
817
963
|
candidateCard,
|
|
964
|
+
interviewFeedbackTodo,
|
|
818
965
|
];
|
|
819
966
|
export const RECRUIT_SECOND_PHASE_WITHHELD = [
|
|
820
967
|
'recruit.candidate.create',
|
|
@@ -8,6 +8,8 @@ const MAX_ROWS_LIMIT = 5000;
|
|
|
8
8
|
const USER_INPUT_LIMIT = 500;
|
|
9
9
|
const BLOCKED_MESSAGE = /拦截|高危|安全校验/;
|
|
10
10
|
const CLARIFY_MESSAGE = /不明确|请补充|补充查询/;
|
|
11
|
+
/** 权限类文案:命中即视为数据权限不足,Agent 必须停在原地,不得改调其它接口拼数据。 */
|
|
12
|
+
const PERMISSION_DENIED_MESSAGE = /权限|越权/;
|
|
11
13
|
function requireUserInput(value) {
|
|
12
14
|
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
13
15
|
throw fnaValidationError('user_input_required', 'userInput 必填(自然语言问题,不能为空白)');
|
|
@@ -65,16 +67,23 @@ function collectColumns(rows) {
|
|
|
65
67
|
}
|
|
66
68
|
return columns;
|
|
67
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* 按文案推断字符串 data 的语义。
|
|
72
|
+
* 权限类文案优先归类为 clarify:服务端可能只返回「无数据权限」而不带「不明确」,
|
|
73
|
+
* 若落进 unexpected,Agent 会误判为无非预期形态,进而换问法或改调其它接口拼数据。
|
|
74
|
+
*/
|
|
68
75
|
function classifyMessage(message) {
|
|
76
|
+
if (PERMISSION_DENIED_MESSAGE.test(message))
|
|
77
|
+
return { kind: 'clarify', permissionDenied: true };
|
|
69
78
|
if (BLOCKED_MESSAGE.test(message))
|
|
70
|
-
return 'blocked';
|
|
79
|
+
return { kind: 'blocked', permissionDenied: false };
|
|
71
80
|
if (CLARIFY_MESSAGE.test(message))
|
|
72
|
-
return 'clarify';
|
|
81
|
+
return { kind: 'clarify', permissionDenied: false };
|
|
73
82
|
return undefined;
|
|
74
83
|
}
|
|
75
84
|
/**
|
|
76
85
|
* 归一化 getExpenseChart 的 data。code 恒为 200,成败只看 data 形态:
|
|
77
|
-
* 对象数组 → rows/empty
|
|
86
|
+
* 对象数组 → rows/empty;追问/安全拦截/无权限文案 → clarify/blocked(按文案推断,无权限额外标 permissionDenied);其余 → unexpected。
|
|
78
87
|
* maxRows 只截断本地返回,列名仍按服务端全量结果收集。
|
|
79
88
|
*/
|
|
80
89
|
function normalizeChart(data, maxRows) {
|
|
@@ -107,9 +116,12 @@ function normalizeChart(data, maxRows) {
|
|
|
107
116
|
});
|
|
108
117
|
}
|
|
109
118
|
if (typeof data === 'string') {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
return fnaSuccess('fna.expense.chart', { kind, message: data }, {
|
|
119
|
+
const classified = classifyMessage(data);
|
|
120
|
+
if (classified) {
|
|
121
|
+
return fnaSuccess('fna.expense.chart', { kind: classified.kind, message: data }, {
|
|
122
|
+
messageKindInferred: true,
|
|
123
|
+
permissionDenied: classified.permissionDenied,
|
|
124
|
+
});
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
return fnaSuccess('fna.expense.chart', { kind: 'unexpected' }, { dataType: dataTypeOf(data) });
|
|
@@ -124,7 +136,7 @@ const fnaExpenseChart = {
|
|
|
124
136
|
resource: 'expense',
|
|
125
137
|
method: 'expense.chart',
|
|
126
138
|
action: 'fna.expense.chart',
|
|
127
|
-
summary: '报销报表 AI 问答:用自然语言查询分组/排名/趋势,返回 kind=rows/empty/clarify/blocked/unexpected
|
|
139
|
+
summary: '报销报表 AI 问答:用自然语言查询分组/排名/趋势,返回 kind=rows/empty/clarify/blocked/unexpected;无权限文案额外返回 meta.permissionDenied=true,此时必须停住、不得改调其它接口拼数据;maxRows 只截断本地展示',
|
|
128
140
|
capability: 'expense-chart',
|
|
129
141
|
risk: READ,
|
|
130
142
|
inputSchema: schemaOf({
|
package/dist/shortcuts/index.js
CHANGED
|
@@ -130,6 +130,11 @@ export const builtinShortcuts = [
|
|
|
130
130
|
description: '泛微E10采知连(文档中心)CLI',
|
|
131
131
|
load: async () => (await import('./caizhilian/index.js')).caizhilianShortcut,
|
|
132
132
|
}),
|
|
133
|
+
lazyShortcut({
|
|
134
|
+
name: 'contract',
|
|
135
|
+
description: '泛微E10今承达合同管理 CLI',
|
|
136
|
+
load: async () => (await import('./contract/index.js')).contractShortcut,
|
|
137
|
+
}),
|
|
133
138
|
];
|
|
134
139
|
/** 全部 shortcut 的 CLI 元数据(不触发任何实现模块加载)。 */
|
|
135
140
|
export function shortcutMetadata() {
|
|
@@ -4,6 +4,7 @@ import { requireE10Session } from '../../internal/e10/context.js';
|
|
|
4
4
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
5
5
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
6
6
|
import { mailAuthError, mailNetworkError, MailCliError, mailValidationError } from './errors.js';
|
|
7
|
+
import { mailPerf } from './operations/types.js';
|
|
7
8
|
function inferProfile(ctx) {
|
|
8
9
|
const options = ctx.getGlobalOptions();
|
|
9
10
|
return options.profile || getActiveProfile();
|
|
@@ -102,7 +103,9 @@ export function createMailHost(ctx, dependencies = {}) {
|
|
|
102
103
|
|| ((innerCtx) => requireE10Session(innerCtx, { skipProfileEnrichment: true }));
|
|
103
104
|
async function ensureSession() {
|
|
104
105
|
if (!e10Session) {
|
|
106
|
+
const sessionStart = Date.now();
|
|
105
107
|
e10Session = await requireSession(ctx);
|
|
108
|
+
mailPerf.sessionMs = Date.now() - sessionStart;
|
|
106
109
|
sessionContext = {
|
|
107
110
|
baseUrl: e10Session.getBaseUrl(),
|
|
108
111
|
profile: inferProfile(ctx),
|
|
@@ -128,6 +131,7 @@ export function createMailHost(ctx, dependencies = {}) {
|
|
|
128
131
|
assertPath(path);
|
|
129
132
|
const resolvedMethod = method || (body === undefined && form === undefined && multipart === undefined ? 'GET' : 'POST');
|
|
130
133
|
let response;
|
|
134
|
+
const reqStart = Date.now();
|
|
131
135
|
try {
|
|
132
136
|
response = await (await runtime()).requestJson({
|
|
133
137
|
module: 'mail',
|
|
@@ -152,6 +156,10 @@ export function createMailHost(ctx, dependencies = {}) {
|
|
|
152
156
|
}
|
|
153
157
|
throw error;
|
|
154
158
|
}
|
|
159
|
+
finally {
|
|
160
|
+
mailPerf.requestCount += 1;
|
|
161
|
+
mailPerf.requestMs += Date.now() - reqStart;
|
|
162
|
+
}
|
|
155
163
|
if (response.status === 401 || response.status === 302) {
|
|
156
164
|
throw mailAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
157
165
|
}
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
export const READ = 'read';
|
|
2
2
|
export const WRITE = 'write';
|
|
3
|
+
/** 进程内耗时收集器(周军锋 2026-10-20 要求:全链路耗时可量化)。host 写入、mailSuccess 注出;内部诊断字段不进回答。 */
|
|
4
|
+
export const mailPerf = {
|
|
5
|
+
t0: Date.now(),
|
|
6
|
+
sessionMs: 0,
|
|
7
|
+
requestCount: 0,
|
|
8
|
+
requestMs: 0,
|
|
9
|
+
};
|
|
3
10
|
export function mailSuccess(action, data, meta) {
|
|
4
11
|
const payload = { ok: true, action, data };
|
|
5
|
-
|
|
6
|
-
|
|
12
|
+
const perf = {
|
|
13
|
+
elapsed_ms: Date.now() - mailPerf.t0,
|
|
14
|
+
session_ms: mailPerf.sessionMs || undefined,
|
|
15
|
+
requests: mailPerf.requestCount || undefined,
|
|
16
|
+
network_ms: mailPerf.requestMs || undefined,
|
|
17
|
+
};
|
|
18
|
+
for (const k of Object.keys(perf))
|
|
19
|
+
if (perf[k] === undefined)
|
|
20
|
+
delete perf[k];
|
|
21
|
+
payload.meta = { ...(meta || {}), ...perf };
|
|
7
22
|
return payload;
|
|
8
23
|
}
|
|
9
24
|
/** 构造「对象类型 + 严格禁止额外字段」的 JSON Schema。 */
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { ExitCode, isCliError, toErrorEnvelope } from '../../core/errors.js';
|
|
3
3
|
import { createMeetingContinuationSigner } from './continuation.js';
|
|
4
4
|
import { createMeetingHost } from './host.js';
|
|
5
|
-
import { meetingManifest } from './manifest.js';
|
|
5
|
+
import { meetingManifest, OPERATION_INDEX_HINT } from './manifest.js';
|
|
6
6
|
import { executeMeetingOperation } from './operations.js';
|
|
7
7
|
import { meetingValidationError } from './errors.js';
|
|
8
8
|
function asOptions(value) {
|
|
@@ -102,6 +102,84 @@ export const meetingShortcut = {
|
|
|
102
102
|
.action(action((ctx) => {
|
|
103
103
|
ctx.output.write(meetingManifest, { json: true });
|
|
104
104
|
}));
|
|
105
|
+
// 能力清单:**纯文本、不压缩**。
|
|
106
|
+
// 为什么必须单独开一条命令:`meeting schema --json` 完整 308KB,stdout 只回 20KB 预览,
|
|
107
|
+
// operations 数组被截到前 10 条 —— agent 拿这个判断"有没有某能力"必然误判
|
|
108
|
+
// (2026-10-30 真机:回复用户"当前 CLI 版本没有建会建议时间操作",实际 suggest 一直存在)。
|
|
109
|
+
meeting.command('ops')
|
|
110
|
+
.description('列出全部会议 operation(纯文本不压缩);判断"有没有某能力"用这个,不要用 schema')
|
|
111
|
+
.option('--grep <keyword>', '只看名字或摘要命中关键词的(不区分大小写)')
|
|
112
|
+
.option('--capability <name>', '只看某个能力分组,如 eb / meeting / room / conflict / sign')
|
|
113
|
+
.option('--json', 'JSON 输出(注意:JSON 走信封压缩,条目多时会被截断,查全量请用文本)')
|
|
114
|
+
.action(action((ctx, ...rest) => {
|
|
115
|
+
// commander 对无位置参数的子命令传的是 (options, command),别取错 —— 用 "没有 opts 方法"
|
|
116
|
+
// 把 command 实例挑出去(2026-10-30:一开始取了最后一个,拿到 command 对象,导致 --grep 失效)。
|
|
117
|
+
const options = rest.find((item) => item && typeof item === 'object' && typeof item.opts !== 'function') ?? {};
|
|
118
|
+
const keyword = String(options?.grep ?? '').trim().toLowerCase();
|
|
119
|
+
const capability = String(options?.capability ?? '').trim().toLowerCase();
|
|
120
|
+
const all = meetingManifest.operations;
|
|
121
|
+
const hit = all.filter((item) => {
|
|
122
|
+
// 分组支持前缀:--capability eb 能命中 eb.meeting / eb.room / eb.sign ...
|
|
123
|
+
if (capability && !String(item.capability ?? '').toLowerCase().startsWith(capability))
|
|
124
|
+
return false;
|
|
125
|
+
if (!keyword)
|
|
126
|
+
return true;
|
|
127
|
+
return item.name.toLowerCase().includes(keyword)
|
|
128
|
+
|| String(item.summary ?? '').toLowerCase().includes(keyword);
|
|
129
|
+
});
|
|
130
|
+
if (options?.json === true) {
|
|
131
|
+
ctx.output.write({
|
|
132
|
+
ok: true,
|
|
133
|
+
schemaVersion: 1,
|
|
134
|
+
operationIndexHint: OPERATION_INDEX_HINT,
|
|
135
|
+
count: hit.length,
|
|
136
|
+
total: all.length,
|
|
137
|
+
operations: hit.map((item) => ({
|
|
138
|
+
name: item.name,
|
|
139
|
+
capability: item.capability,
|
|
140
|
+
risk: item.risk,
|
|
141
|
+
summary: item.summary,
|
|
142
|
+
})),
|
|
143
|
+
withheldOperations: meetingManifest.withheldOperations,
|
|
144
|
+
}, { json: true });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const clip = (text, max) => (text.length > max ? `${text.slice(0, max)}…` : text);
|
|
148
|
+
const lines = [
|
|
149
|
+
`泛微E10会议 CLI 能力清单:命中 ${hit.length} / 共 ${all.length} 项`
|
|
150
|
+
+ `${keyword ? `(关键词「${keyword}」)` : ''}${capability ? `(分组 ${capability})` : ''}`,
|
|
151
|
+
'用法:weaver-work-cli --json meeting run <operation> --input-json \'{...}\'',
|
|
152
|
+
'',
|
|
153
|
+
];
|
|
154
|
+
const groups = new Map();
|
|
155
|
+
for (const item of hit) {
|
|
156
|
+
const key = String(item.capability ?? 'other');
|
|
157
|
+
if (!groups.has(key))
|
|
158
|
+
groups.set(key, []);
|
|
159
|
+
groups.get(key).push(item);
|
|
160
|
+
}
|
|
161
|
+
for (const [group, items] of [...groups.entries()].sort((a, b) => b[1].length - a[1].length)) {
|
|
162
|
+
lines.push(`## ${group}(${items.length})`);
|
|
163
|
+
for (const item of items) {
|
|
164
|
+
lines.push(` ${item.name} ${clip(String(item.summary ?? ''), 96)}`);
|
|
165
|
+
}
|
|
166
|
+
lines.push('');
|
|
167
|
+
}
|
|
168
|
+
if (!capability || 'withheld'.startsWith(capability)) {
|
|
169
|
+
const withheld = meetingManifest.withheldOperations.filter((item) => {
|
|
170
|
+
if (!keyword)
|
|
171
|
+
return true;
|
|
172
|
+
return item.name.toLowerCase().includes(keyword) || item.reason.toLowerCase().includes(keyword);
|
|
173
|
+
});
|
|
174
|
+
if (withheld.length > 0) {
|
|
175
|
+
lines.push(`## 不可用(${withheld.length})`);
|
|
176
|
+
for (const item of withheld) {
|
|
177
|
+
lines.push(` ${item.name} ${clip(item.reason, 120)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
182
|
+
}));
|
|
105
183
|
meeting.command('run')
|
|
106
184
|
.description('Run a meeting operation with JSON input')
|
|
107
185
|
.argument('<operation>', 'operation name, e.g. meeting.room.list')
|
|
@@ -62,8 +62,18 @@ function buildOperations() {
|
|
|
62
62
|
inputSchema: definition.inputSchema,
|
|
63
63
|
}));
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* 给模型的**能力自查提示**。放在 manifest 靠前位置、且控制在 200 字符内 ——
|
|
67
|
+
* 输出压缩时字符串最多留 200 字符,这句话必须能完整活下来。
|
|
68
|
+
* 2026-10-30 真机事故:`meeting schema --json` 完整 308KB、预览只给 20KB,
|
|
69
|
+
* operations 数组被截到前 10 条,`meeting.eb.meeting.create.suggest` 根本不在预览里,
|
|
70
|
+
* agent 于是回复用户"当前 CLI 版本没有建会建议时间操作"——能力是有的,是被压缩藏住了。
|
|
71
|
+
*/
|
|
72
|
+
export const OPERATION_INDEX_HINT = '本 JSON 可能被压缩截断,别据此判断能力是否存在;'
|
|
73
|
+
+ '完整清单跑 `weaver-work-cli meeting ops`(纯文本、不压缩,支持 --grep 关键词)';
|
|
65
74
|
export const meetingManifest = {
|
|
66
75
|
schemaVersion: 1,
|
|
76
|
+
operationIndexHint: OPERATION_INDEX_HINT,
|
|
67
77
|
name: 'meeting',
|
|
68
78
|
title: '泛微E10会议管理(双模式:标准版 Java REST + EB 版 eBuilder)',
|
|
69
79
|
version: '1.0.0',
|
|
@@ -204,6 +204,11 @@ export const EB_ENDPOINTS = [
|
|
|
204
204
|
interfacePk: 'query_meeting_room_list',
|
|
205
205
|
countPk: 'query_meeting_room_count',
|
|
206
206
|
queryParams: ['manager', 'departmentId', 'mr_type', 'room_attr'],
|
|
207
|
+
// 按 e-builder openApi 文档:这些是 `mainTable` 节点的传入字段,服务端据此过滤。
|
|
208
|
+
// ⚠️ 2026-10-30 实测(用户刚在后端把「会议室名称」配成传入字段后):
|
|
209
|
+
// 服务端是**精确匹配** —— room_name=测试会议室1 → 1 条;room_name=测试 / 重庆 → 0 条
|
|
210
|
+
// (全量里明明有「重庆会议室一/二」)。模糊由 factory 的 fallback 在本地兜底。
|
|
211
|
+
mainTableParams: ['room_name', 'status', 'place', 'org_id', 'hrm_ids'],
|
|
207
212
|
summarizeRows: summarizeRoomRows,
|
|
208
213
|
summary: 'EB 版会议室列表(query_meeting_room_list + count):自带 statusText 与状态字典',
|
|
209
214
|
emptyHint: 'EB 会议室表(`uf_mt_room_base`)查不到任何记录。可能是:① 该环境确实还没建过会议室;'
|
|
@@ -136,7 +136,12 @@ export async function fetchEbAppSettings(env, operation) {
|
|
|
136
136
|
* - 开关为空 / 非 `1` → 放行(交由后续冲突检测与用户确认)。
|
|
137
137
|
*
|
|
138
138
|
* 拒绝后 CLI 不代为发起流程(流程集成不在本模块范围),只把已确认的信息交回调用方,
|
|
139
|
-
*
|
|
139
|
+
* 由调用方改用 `meeting.eb.meeting.create.flow.prepare/quick`(按 `mt_new_wf` 走流程建会)。
|
|
140
|
+
*
|
|
141
|
+
* ⚠️ 2026-10-29 更正:这里原先写的是"由调用方转 `weaver-e10-workflow`",是过时口径 ——
|
|
142
|
+
* 转去通用 workflow 模块会拿不到会议表单字段映射/会议室联动/邀请函,且容易让 agent
|
|
143
|
+
* 用关键词检索流程(真实事故:检索出「会议室预定流程-北方大区业务运营中心」当配置项)。
|
|
144
|
+
* 会议流程必须走 `create.flow`。
|
|
140
145
|
*/
|
|
141
146
|
export function assertEbCreatePathAllowed(settings) {
|
|
142
147
|
if (settings.error) {
|