weaver-work-cli 0.1.21 → 0.1.22
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/shortcuts/invoice/host.js +5 -4
- package/dist/shortcuts/meeting/operations/eb/association.js +151 -0
- package/dist/shortcuts/meeting/operations/eb/attendance-summary.js +153 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +185 -13
- package/dist/shortcuts/meeting/operations/eb/conflict.js +17 -11
- package/dist/shortcuts/meeting/operations/eb/decision.js +385 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +222 -35
- package/dist/shortcuts/meeting/operations/eb/index.js +18 -1
- package/dist/shortcuts/meeting/operations/eb/link.js +212 -0
- package/dist/shortcuts/meeting/operations/eb/task-progress.js +123 -0
- package/dist/shortcuts/meeting/operations/env.js +78 -61
- package/dist/shortcuts/meeting/operations/link.js +125 -0
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/shared.js +13 -0
- package/docs/e10-auth.md +4 -0
- package/docs/invoice.md +3 -0
- package/package.json +1 -1
- package/skills/weaver-e10-meeting/SKILL.md +26 -10
- package/skills/weaver-e10-meeting/references/meeting-eb.md +162 -7
- package/skills/weaver-e10-meeting/references/meeting-verification.md +271 -0
- package/skills/weaver-e10-meeting.zip +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 决议任务的**进度汇总**(供 `meeting.eb.decisionTask.list` 声明式挂载,见 catalog 的 `summarizeRows`)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要(2026-09-21/22 真机确立的口径,三个坑每次手算都容易踩):
|
|
5
|
+
* 1. **任务主表的 `decision_status` 恒为「未完成」** —— 既不随明细变化,也无法经接口写入,
|
|
6
|
+
* 所以"任务完成了没"**只能**从 `detail1`(执行人跟进记录)读;
|
|
7
|
+
* 2. **`executors` 可能是单对象也可能是数组**(1 人 = 对象,多人 = 数组),直接取 length 会得到 undefined;
|
|
8
|
+
* 3. **`detail1` 只有"已跟进过"的人才有记录** —— 没跟进的执行人不在里面,
|
|
9
|
+
* 若按 detail1 统计人数会漏人(表现为"1/1 完成",其实还有个人没跟进)。
|
|
10
|
+
*
|
|
11
|
+
* 故这里统一:**以 `executors` 为人员基准,用 `detail1` 覆盖其状态,缺失的人补 `pending`**。
|
|
12
|
+
* 状态归一化成英文枚举便于程序判断;中文展示交给调用方翻译。
|
|
13
|
+
*/
|
|
14
|
+
function toText(value) {
|
|
15
|
+
return value === undefined || value === null ? '' : String(value);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `finish_status` 归一化。
|
|
19
|
+
*
|
|
20
|
+
* EB 在不同接口/配置下可能回**中文渲染值**(已完成 / 已延期 / 未完成)或**数字 id**(0/1/2),
|
|
21
|
+
* 两种都兼容;无法识别的一律按 `pending`(宁可少报完成,不夸大)。
|
|
22
|
+
*/
|
|
23
|
+
function normalizeFinishStatus(raw) {
|
|
24
|
+
const text = toText(raw).trim();
|
|
25
|
+
if (text === '')
|
|
26
|
+
return 'pending';
|
|
27
|
+
if (text === '1' || text.includes('已完成') || text === '完成')
|
|
28
|
+
return 'done';
|
|
29
|
+
if (text === '2' || text.includes('延期'))
|
|
30
|
+
return 'overdue';
|
|
31
|
+
return 'pending';
|
|
32
|
+
}
|
|
33
|
+
/** 人员字段(`executors` / `executor`)兼容"单对象 | 数组 | 纯 id 串",统一成 `[{id,name}]`。 */
|
|
34
|
+
function toPeople(raw) {
|
|
35
|
+
const list = raw === undefined || raw === null ? [] : (Array.isArray(raw) ? raw : [raw]);
|
|
36
|
+
const out = [];
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
for (const item of list) {
|
|
39
|
+
if (item === undefined || item === null)
|
|
40
|
+
continue;
|
|
41
|
+
if (typeof item === 'object') {
|
|
42
|
+
const id = toText(item.id).trim();
|
|
43
|
+
if (id === '' || seen.has(id))
|
|
44
|
+
continue;
|
|
45
|
+
seen.add(id);
|
|
46
|
+
out.push({ id, name: toText(item.name) });
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const id = toText(item).trim();
|
|
50
|
+
if (id === '' || seen.has(id))
|
|
51
|
+
continue;
|
|
52
|
+
seen.add(id);
|
|
53
|
+
out.push({ id, name: '' });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** 单行 → 任务进度。 */
|
|
59
|
+
function summarizeRow(row) {
|
|
60
|
+
const main = (row?.mainTable ?? row ?? {});
|
|
61
|
+
const mt = (main.mt_id ?? {});
|
|
62
|
+
// detail1:只有已跟进的人才有记录 ⇒ 先建索引,再按 executors 对齐(缺的补 pending)。
|
|
63
|
+
const followed = new Map();
|
|
64
|
+
const details = Array.isArray(row?.detail1) ? row.detail1 : [];
|
|
65
|
+
for (const item of details) {
|
|
66
|
+
const who = toPeople(item?.executor)[0];
|
|
67
|
+
if (!who)
|
|
68
|
+
continue;
|
|
69
|
+
followed.set(who.id, {
|
|
70
|
+
status: normalizeFinishStatus(item?.finish_status),
|
|
71
|
+
finishDate: toText(item?.finish_date),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const members = toPeople(main.executors).map((person) => {
|
|
75
|
+
const hit = followed.get(person.id);
|
|
76
|
+
return {
|
|
77
|
+
executorId: person.id,
|
|
78
|
+
executorName: person.name,
|
|
79
|
+
status: hit ? hit.status : 'pending',
|
|
80
|
+
finishDate: hit ? hit.finishDate : '',
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
// 兜底:主表没带 executors 时退化为按跟进记录统计,避免显示 0/0 让人误判"没有执行人"。
|
|
84
|
+
if (members.length === 0) {
|
|
85
|
+
for (const [id, hit] of followed) {
|
|
86
|
+
members.push({ executorId: id, executorName: '', status: hit.status, finishDate: hit.finishDate });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const done = members.filter((item) => item.status === 'done').length;
|
|
90
|
+
const overdue = members.filter((item) => item.status === 'overdue').length;
|
|
91
|
+
const pending = members.length - done - overdue;
|
|
92
|
+
const status = members.length === 0
|
|
93
|
+
? 'pending'
|
|
94
|
+
: done === members.length
|
|
95
|
+
? 'done'
|
|
96
|
+
: overdue > 0
|
|
97
|
+
? 'overdue'
|
|
98
|
+
: done > 0
|
|
99
|
+
? 'inProgress'
|
|
100
|
+
: 'pending';
|
|
101
|
+
return {
|
|
102
|
+
id: toText(main.id),
|
|
103
|
+
title: toText(main.decision_title),
|
|
104
|
+
mtId: toText(mt.id),
|
|
105
|
+
mtName: toText(mt.name),
|
|
106
|
+
plan: { begin: toText(main.begin_datetime), end: toText(main.end_datetime) },
|
|
107
|
+
progress: { total: members.length, done, overdue, pending, status, members },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** `decisionTask.list` 的结果后处理:原始 `rows` 原样保留,这里只额外给出 `progress`。 */
|
|
111
|
+
export function summarizeTaskProgress(rows) {
|
|
112
|
+
const items = rows.map((row) => summarizeRow(row ?? {}));
|
|
113
|
+
return {
|
|
114
|
+
summary: {
|
|
115
|
+
total: items.length,
|
|
116
|
+
done: items.filter((item) => item.progress.status === 'done').length,
|
|
117
|
+
overdue: items.filter((item) => item.progress.status === 'overdue').length,
|
|
118
|
+
inProgress: items.filter((item) => item.progress.status === 'inProgress').length,
|
|
119
|
+
pending: items.filter((item) => item.progress.status === 'pending').length,
|
|
120
|
+
},
|
|
121
|
+
items,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -16,6 +16,13 @@ function stateRoot(ctx) {
|
|
|
16
16
|
return resolve(configured, '..');
|
|
17
17
|
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* 会议模块状态目录(`<stateRoot>/meeting`):环境模式缓存、EB objid 缓存等落在这里。
|
|
21
|
+
* 导出供 link.ts 复用,避免各文件各自推导 stateRoot 导致缓存分裂。
|
|
22
|
+
*/
|
|
23
|
+
export function meetingStateDir(ctx) {
|
|
24
|
+
return resolve(stateRoot(ctx), 'meeting');
|
|
25
|
+
}
|
|
19
26
|
function cacheFile(ctx) {
|
|
20
27
|
return resolve(stateRoot(ctx), 'meeting', 'env-mode.json');
|
|
21
28
|
}
|
|
@@ -50,6 +57,74 @@ async function writeCache(ctx, baseUrl, mode, smokePath) {
|
|
|
50
57
|
return `环境模式缓存写入失败(不影响本次结果,下次会重新冒烟):${error instanceof Error ? error.message : String(error)}`;
|
|
51
58
|
}
|
|
52
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* 解析当前环境的会议模式(EB / 标准版),供 `meeting.env.detect` 与 `meeting.link`
|
|
62
|
+
* 共用同一套缓存与冒烟逻辑(避免两处判定不一致导致链接模式选错)。
|
|
63
|
+
*/
|
|
64
|
+
export async function resolveMeetingEnvMode(host, ctx, options = {}) {
|
|
65
|
+
const session = await host.getSession();
|
|
66
|
+
// ① 默认先读缓存(按 baseUrl 固化);refresh=true 才强制重新冒烟。
|
|
67
|
+
if (options.refresh !== true) {
|
|
68
|
+
const cache = await readCache(ctx);
|
|
69
|
+
const cached = cache.environments[session.baseUrl];
|
|
70
|
+
if (cached && (cached.mode === 'eb' || cached.mode === 'standard')) {
|
|
71
|
+
return {
|
|
72
|
+
mode: cached.mode,
|
|
73
|
+
smokePath: cached.smokePath,
|
|
74
|
+
fromCache: true,
|
|
75
|
+
detectedAt: cached.detectedAt,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ② 第 1 步:EB 版冒烟(EB 优先)。接口 404/业务失败均视为「EB 未配置」。
|
|
80
|
+
let ebError;
|
|
81
|
+
try {
|
|
82
|
+
await host.request({
|
|
83
|
+
method: 'POST',
|
|
84
|
+
path: EB_SMOKE_PATH,
|
|
85
|
+
operation: 'meeting.env.detect',
|
|
86
|
+
// EB list 的 datajson 内必须出现 mainTable 节点(空对象即可):缺失时服务端报
|
|
87
|
+
// “mainTable解析异常,请参照文档修正数据格式”,冒烟会**假失败**并把 EB 环境误判为
|
|
88
|
+
// 标准版(2026-09-20 真机复现)。
|
|
89
|
+
body: { datajson: { mainTable: {}, header: { operator: session.userId } } },
|
|
90
|
+
// EB 响应不含 code/status(形如 {datajson:{datas:[...]}}),默认的 isBusinessSuccess
|
|
91
|
+
// 会判为 false → 冒烟假失败 → EB 环境被误判为标准版。
|
|
92
|
+
acceptBusiness: ebBusinessSuccess,
|
|
93
|
+
});
|
|
94
|
+
const warning = await writeCache(ctx, session.baseUrl, 'eb', EB_SMOKE_PATH);
|
|
95
|
+
return { mode: 'eb', smokePath: EB_SMOKE_PATH, fromCache: false, warning };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
ebError = error;
|
|
99
|
+
}
|
|
100
|
+
// ③ 第 2 步:标准版冒烟(仅当 EB 冒烟未通过)。
|
|
101
|
+
try {
|
|
102
|
+
await host.request({
|
|
103
|
+
method: 'GET',
|
|
104
|
+
path: STD_CALENDAR_PATH,
|
|
105
|
+
operation: 'meeting.env.detect',
|
|
106
|
+
});
|
|
107
|
+
const ebDetail = ebError instanceof Error
|
|
108
|
+
? `${ebError.type ?? 'api'}/${ebError.subtype ?? 'error'}: ${ebError.message}`
|
|
109
|
+
: String(ebError);
|
|
110
|
+
const warning = await writeCache(ctx, session.baseUrl, 'standard', STD_CALENDAR_PATH);
|
|
111
|
+
return {
|
|
112
|
+
mode: 'standard',
|
|
113
|
+
smokePath: STD_CALENDAR_PATH,
|
|
114
|
+
fromCache: false,
|
|
115
|
+
ebSmokeFailed: true,
|
|
116
|
+
ebSmokeError: ebDetail,
|
|
117
|
+
warning,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (stdError) {
|
|
121
|
+
// 两步都不通 → 不写缓存,提示接口未配置完整。
|
|
122
|
+
throw meetingValidationError('env_unavailable', '当前环境 EB 版与标准版会议接口冒烟均未通过,请确认会议应用已安装且接口已配置完整', {
|
|
123
|
+
eb: ebError instanceof Error ? ebError.message : String(ebError),
|
|
124
|
+
standard: stdError instanceof Error ? stdError.message : String(stdError),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
53
128
|
const meetingEnvDetect = {
|
|
54
129
|
name: 'meeting.env.detect',
|
|
55
130
|
resource: 'env',
|
|
@@ -67,67 +142,9 @@ const meetingEnvDetect = {
|
|
|
67
142
|
},
|
|
68
143
|
},
|
|
69
144
|
handler: async (args, { host, ctx }) => {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const cache = await readCache(ctx);
|
|
74
|
-
const cached = cache.environments[session.baseUrl];
|
|
75
|
-
if (cached && (cached.mode === 'eb' || cached.mode === 'standard')) {
|
|
76
|
-
return meetingSuccess('meeting.env.detect', {
|
|
77
|
-
mode: cached.mode,
|
|
78
|
-
smokePath: cached.smokePath,
|
|
79
|
-
fromCache: true,
|
|
80
|
-
detectedAt: cached.detectedAt,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// ② 第 1 步:EB 版冒烟(EB 优先)。接口 404/业务失败均视为「EB 未配置」。
|
|
85
|
-
let ebError;
|
|
86
|
-
try {
|
|
87
|
-
await host.request({
|
|
88
|
-
method: 'POST',
|
|
89
|
-
path: EB_SMOKE_PATH,
|
|
90
|
-
operation: 'meeting.env.detect',
|
|
91
|
-
// EB list 的 datajson 内必须出现 mainTable 节点(空对象即可):缺失时服务端报
|
|
92
|
-
// “mainTable解析异常,请参照文档修正数据格式”,冒烟会**假失败**并把 EB 环境误判为
|
|
93
|
-
// 标准版(2026-09-20 真机复现)。
|
|
94
|
-
body: { datajson: { mainTable: {}, header: { operator: session.userId } } },
|
|
95
|
-
// EB 响应不含 code/status(形如 {datajson:{datas:[...]}}),默认的 isBusinessSuccess
|
|
96
|
-
// 会判为 false → 冒烟假失败 → EB 环境被误判为标准版。
|
|
97
|
-
acceptBusiness: ebBusinessSuccess,
|
|
98
|
-
});
|
|
99
|
-
const warning = await writeCache(ctx, session.baseUrl, 'eb', EB_SMOKE_PATH);
|
|
100
|
-
return meetingSuccess('meeting.env.detect', { mode: 'eb', smokePath: EB_SMOKE_PATH, fromCache: false }, warning ? { warnings: [warning] } : undefined);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
ebError = error;
|
|
104
|
-
}
|
|
105
|
-
// ③ 第 2 步:标准版冒烟(仅当 EB 冒烟未通过)。
|
|
106
|
-
try {
|
|
107
|
-
await host.request({
|
|
108
|
-
method: 'GET',
|
|
109
|
-
path: STD_CALENDAR_PATH,
|
|
110
|
-
operation: 'meeting.env.detect',
|
|
111
|
-
});
|
|
112
|
-
const ebDetail = ebError instanceof Error
|
|
113
|
-
? `${ebError.type ?? 'api'}/${ebError.subtype ?? 'error'}: ${ebError.message}`
|
|
114
|
-
: String(ebError);
|
|
115
|
-
const warning = await writeCache(ctx, session.baseUrl, 'standard', STD_CALENDAR_PATH);
|
|
116
|
-
return meetingSuccess('meeting.env.detect', {
|
|
117
|
-
mode: 'standard',
|
|
118
|
-
smokePath: STD_CALENDAR_PATH,
|
|
119
|
-
fromCache: false,
|
|
120
|
-
ebSmokeFailed: true,
|
|
121
|
-
ebSmokeError: ebDetail,
|
|
122
|
-
}, warning ? { warnings: [warning] } : undefined);
|
|
123
|
-
}
|
|
124
|
-
catch (stdError) {
|
|
125
|
-
// 两步都不通 → 不写缓存,提示接口未配置完整。
|
|
126
|
-
throw meetingValidationError('env_unavailable', '当前环境 EB 版与标准版会议接口冒烟均未通过,请确认会议应用已安装且接口已配置完整', {
|
|
127
|
-
eb: ebError instanceof Error ? ebError.message : String(ebError),
|
|
128
|
-
standard: stdError instanceof Error ? stdError.message : String(stdError),
|
|
129
|
-
});
|
|
130
|
-
}
|
|
145
|
+
const result = await resolveMeetingEnvMode(host, ctx, { refresh: args.refresh === true });
|
|
146
|
+
const { warning, ...data } = result;
|
|
147
|
+
return meetingSuccess('meeting.env.detect', data, warning ? { warnings: [warning] } : undefined);
|
|
131
148
|
},
|
|
132
149
|
};
|
|
133
150
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { meetingValidationError } from '../errors.js';
|
|
2
|
+
import { meetingSuccess, schemaOf, SCHEMA_ID, SCHEMA_ID_LIST } from './types.js';
|
|
3
|
+
import { resolveMeetingEnvMode } from './env.js';
|
|
4
|
+
import { MEETING_EB_TAGS, STANDARD_NO_PAGE_TYPES, buildEbCardPath, buildStandardMeetingPath, meetingAbsoluteUrl, meetingMarkdownLink, resolveMeetingEbObjs, } from './eb/link.js';
|
|
5
|
+
const EB_TYPES = Object.keys(MEETING_EB_TAGS);
|
|
6
|
+
/**
|
|
7
|
+
* `meeting.link` —— 会议对象「打开链接」生成器(EB 版 + 标准版)。
|
|
8
|
+
*
|
|
9
|
+
* 为什么要单独一个 operation:链接地址随模式不同、且 EB 版需要按表 tag 动态解析 objid
|
|
10
|
+
* (objid 随环境变化、禁止硬编码,也不等于 formId),不能让 Agent 手工拼 URL。
|
|
11
|
+
* 解析失败只降级为「不附链接」并写 warnings,**不影响**主业务结果。
|
|
12
|
+
*/
|
|
13
|
+
const meetingLink = {
|
|
14
|
+
name: 'meeting.link',
|
|
15
|
+
resource: 'link',
|
|
16
|
+
method: 'resolve',
|
|
17
|
+
action: 'meeting.link',
|
|
18
|
+
summary: '生成对象详情页链接:EB 版 /sp/ebdfpage/card/0/{objid}/{dataid}(按 tag 解析 objid 并缓存);标准版会议 /sp/meeting/meetingSingle?meetingId=(会议室/会议类型无单页,不附链接)',
|
|
19
|
+
capability: 'link',
|
|
20
|
+
risk: 'read',
|
|
21
|
+
inputSchema: schemaOf({
|
|
22
|
+
dataId: { ...SCHEMA_ID, description: '对象 id:标准版=会议 id;EB 版=表单数据 id(单条查询用)' },
|
|
23
|
+
dataIds: { ...SCHEMA_ID_LIST, description: '批量:一次为多条数据生成链接(列表输出用)' },
|
|
24
|
+
type: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: [...EB_TYPES],
|
|
27
|
+
description: '对象类别(默认 meeting)。映射 EB 表 tag,详见 references/meeting-eb.md 的链接章节',
|
|
28
|
+
},
|
|
29
|
+
tag: { type: 'string', description: '直接指定 EB 表 tag(如 uf_mt_detail),与 type 二选一,优先于 type' },
|
|
30
|
+
mode: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: ['auto', 'eb', 'standard'],
|
|
33
|
+
description: 'auto(默认)=按当前环境模式自动判定;也可显式指定',
|
|
34
|
+
},
|
|
35
|
+
name: { type: 'string', description: '链接显示名;传入后同时返回 markdown 内联链接' },
|
|
36
|
+
refresh: { type: 'boolean', description: 'true=强制重新解析 EB objid(忽略缓存)' },
|
|
37
|
+
}, { required: [] }),
|
|
38
|
+
handler: async (args, { host, ctx }) => {
|
|
39
|
+
const single = args.dataId !== undefined && args.dataId !== null && String(args.dataId).trim() !== '';
|
|
40
|
+
const batch = Array.isArray(args.dataIds)
|
|
41
|
+
? args.dataIds.map((item) => String(item)).filter((item) => item.trim() !== '')
|
|
42
|
+
: [];
|
|
43
|
+
const dataIds = single ? [String(args.dataId)] : batch;
|
|
44
|
+
if (dataIds.length === 0) {
|
|
45
|
+
throw meetingValidationError('link_data_id_required', '生成链接必须提供 dataId(单条)或 dataIds(批量)');
|
|
46
|
+
}
|
|
47
|
+
const requestedType = args.type === undefined ? 'meeting' : String(args.type);
|
|
48
|
+
const tag = typeof args.tag === 'string' && args.tag.trim() !== ''
|
|
49
|
+
? args.tag.trim()
|
|
50
|
+
: MEETING_EB_TAGS[requestedType];
|
|
51
|
+
if (!tag) {
|
|
52
|
+
throw meetingValidationError('link_type_invalid', `未知对象类别:${requestedType}(可用:${EB_TYPES.join(' / ')},或用 tag 直接指定表 tag)`);
|
|
53
|
+
}
|
|
54
|
+
const session = await host.getSession();
|
|
55
|
+
const warnings = [];
|
|
56
|
+
const requestedMode = args.mode === undefined ? 'auto' : String(args.mode);
|
|
57
|
+
let mode;
|
|
58
|
+
if (requestedMode === 'eb' || requestedMode === 'standard') {
|
|
59
|
+
mode = requestedMode;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const detected = await resolveMeetingEnvMode(host, ctx, {});
|
|
63
|
+
mode = detected.mode;
|
|
64
|
+
if (detected.warning)
|
|
65
|
+
warnings.push(detected.warning);
|
|
66
|
+
}
|
|
67
|
+
let objId = null;
|
|
68
|
+
let appId = null;
|
|
69
|
+
let fromCache = false;
|
|
70
|
+
if (mode === 'standard') {
|
|
71
|
+
// 标准版:只有会议详情有单页;会议室 / 会议类型明确无单页(standalone 口径)。
|
|
72
|
+
if (STANDARD_NO_PAGE_TYPES.includes(requestedType)) {
|
|
73
|
+
warnings.push(`标准版「${requestedType}」没有单页地址,按口径只输出数据、不附链接`);
|
|
74
|
+
}
|
|
75
|
+
else if (requestedType !== 'meeting') {
|
|
76
|
+
warnings.push(`标准版未记录「${requestedType}」的单页地址,已跳过链接生成`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const resolved = await resolveMeetingEbObjs(host, ctx, { tags: [tag], refresh: args.refresh === true });
|
|
81
|
+
objId = resolved.objs[tag]?.objId ?? null;
|
|
82
|
+
appId = resolved.appId;
|
|
83
|
+
fromCache = resolved.fromCache;
|
|
84
|
+
warnings.push(...resolved.warnings);
|
|
85
|
+
if (!objId)
|
|
86
|
+
warnings.push(`未解析到 tag=${tag} 的 objid,本次不附链接`);
|
|
87
|
+
}
|
|
88
|
+
const noStdPage = mode === 'standard' && requestedType !== 'meeting';
|
|
89
|
+
const items = dataIds.map((dataId) => {
|
|
90
|
+
const path = mode === 'eb'
|
|
91
|
+
? (objId ? buildEbCardPath(objId, dataId) : null)
|
|
92
|
+
: (noStdPage ? null : buildStandardMeetingPath(dataId));
|
|
93
|
+
const url = meetingAbsoluteUrl(session.baseUrl, path);
|
|
94
|
+
const item = { dataId, path, url };
|
|
95
|
+
const markdown = typeof args.name === 'string' && args.name.trim() !== ''
|
|
96
|
+
? meetingMarkdownLink(args.name, url)
|
|
97
|
+
: undefined;
|
|
98
|
+
if (markdown)
|
|
99
|
+
item.markdown = markdown;
|
|
100
|
+
return item;
|
|
101
|
+
});
|
|
102
|
+
// source:objid 的来源(cache/dynamic);标准版不需要 objid ⇒ none。
|
|
103
|
+
const source = mode === 'eb' ? (objId ? (fromCache ? 'cache' : 'dynamic') : 'none') : 'none';
|
|
104
|
+
const data = {
|
|
105
|
+
mode,
|
|
106
|
+
type: requestedType,
|
|
107
|
+
// 标准版不涉及 EB 表 tag,返回 null 避免误用。
|
|
108
|
+
tag: mode === 'eb' ? tag : null,
|
|
109
|
+
baseUrl: session.baseUrl,
|
|
110
|
+
appId,
|
|
111
|
+
objId,
|
|
112
|
+
source,
|
|
113
|
+
items,
|
|
114
|
+
};
|
|
115
|
+
if (single) {
|
|
116
|
+
const [first] = items;
|
|
117
|
+
data.path = first.path;
|
|
118
|
+
data.url = first.url;
|
|
119
|
+
if (first.markdown)
|
|
120
|
+
data.markdown = first.markdown;
|
|
121
|
+
}
|
|
122
|
+
return meetingSuccess('meeting.link', data, warnings.length ? { warnings } : {});
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
export const meetingLinkOperations = [meetingLink];
|
|
@@ -9,6 +9,7 @@ import { meetingCreateOperations } from './create.js';
|
|
|
9
9
|
import { meetingLifecycleOperations } from './lifecycle.js';
|
|
10
10
|
import { meetingReceiptOperations } from './receipts.js';
|
|
11
11
|
import { meetingSignOperations } from './signs.js';
|
|
12
|
+
import { meetingLinkOperations } from './link.js';
|
|
12
13
|
import { meetingEbOperations } from './eb/index.js';
|
|
13
14
|
function buildRegistry() {
|
|
14
15
|
const registry = new Map();
|
|
@@ -23,6 +24,7 @@ function buildRegistry() {
|
|
|
23
24
|
...meetingChangeOperations,
|
|
24
25
|
...meetingReceiptOperations,
|
|
25
26
|
...meetingSignOperations,
|
|
27
|
+
...meetingLinkOperations,
|
|
26
28
|
...meetingEbOperations,
|
|
27
29
|
];
|
|
28
30
|
for (const definition of definitions) {
|
|
@@ -29,6 +29,19 @@ export function assertDateTimeMinute(value, label) {
|
|
|
29
29
|
}
|
|
30
30
|
return value;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 校验 `yyyy-MM-dd HH:mm:ss`(**带秒**)—— EB 部分字段要求带秒。
|
|
34
|
+
*
|
|
35
|
+
* 2026-09-21 真机实测:决议任务的 `finish_date` 只接受带秒格式,传 `2026-09-21 18:30`
|
|
36
|
+
* 会被服务端拒绝(「不满足传入格式yyyy-MM-dd HH:mm:ss」)。而决议明细的
|
|
37
|
+
* `begin_datetime`/`end_datetime` 是不带秒的 —— **同一应用里两种格式并存**,故分别校验。
|
|
38
|
+
*/
|
|
39
|
+
export function assertDateTimeSecond(value, label) {
|
|
40
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) {
|
|
41
|
+
throw meetingValidationError('datetime_invalid', `${label} 必须是 yyyy-MM-dd HH:mm:ss 格式(带秒)`);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
32
45
|
/** 校验 yyyy-MM-dd,返回原串。 */
|
|
33
46
|
export function assertDate(value, label) {
|
|
34
47
|
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
package/docs/e10-auth.md
CHANGED
|
@@ -10,6 +10,10 @@ E10 适配模块刻意与核心 CLI 框架隔离,避免平台逻辑污染通
|
|
|
10
10
|
- Keychain service/account:`weaver-work-cli` / `auth-key`
|
|
11
11
|
- 发票业务 Token Keychain service:`cn.weaver.weaver-work-cli.invoice-token`
|
|
12
12
|
|
|
13
|
+
发票业务 Token 的 Keychain 缓存是可选的性能优化。在没有系统密钥环的
|
|
14
|
+
Web/Linux 环境中,CLI 不会把 Token 写入明文文件,而是仅在当前命令进程内保存并
|
|
15
|
+
继续完成业务请求;下一次命令会重新向 E10 获取 Token。
|
|
16
|
+
|
|
13
17
|
`weaver-work-cli` 默认不读取、不迁移 `e10-login` / `e10-cli` 的旧 `.e10-cli`
|
|
14
18
|
登录态,避免用户安装本插件后因为历史登录状态被自动授权。确需复用外部 auth
|
|
15
19
|
文件时,可以显式传 `--auth <path>` 或设置 `E10_AUTH_PATH`。
|
package/docs/invoice.md
CHANGED
|
@@ -21,6 +21,9 @@ weaver-work-cli auth status --no-check
|
|
|
21
21
|
2. 调用或复用 `/api/inc/proxy/getToken?appType=1` 取得发票业务 Token。
|
|
22
22
|
3. 业务请求同时携带 E10 Cookie/ETEAMSID 和 `token` Header。
|
|
23
23
|
|
|
24
|
+
系统 Keychain/Secret Service 不可用时,发票 Token 只保存在当前命令进程内,
|
|
25
|
+
安全缓存失败不会阻断票夹查询;CLI 不会降级为明文 Token 文件。
|
|
26
|
+
|
|
24
27
|
Agent 不得在业务 JSON 中传 `access_token`、`app_key`、`app_secret`、Cookie、ETEAMSID 或发票 Token。
|
|
25
28
|
|
|
26
29
|
## Agent 入口
|
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
name: weaver-e10-meeting
|
|
3
3
|
display_name: 泛微E10会议管理
|
|
4
4
|
display_name_en: Weaver E10 Meeting Management
|
|
5
|
-
description: "泛微 E10 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]
|
|
6
|
-
description_zh: "泛微 E10 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]
|
|
7
|
-
description_en: "Weaver E10 meeting management for daily meeting tasks: [Meetings] query today's / this week's / my meetings and meeting details, book meetings with automatic room & attendee conflict checks, reschedule / change location / change attendees, cancel, delete; [Rooms] query meeting rooms and occupancy / availability; [Attendees & receipts] view attendee lists, fill in / update meeting receipts; [Sign-in] view sign-in / sign-out status, manual sign-in, manual sign-out, clear sign-in records; [Meeting content] meeting agenda (view / update / delete), agenda types, minutes query, meeting types and
|
|
8
|
-
version: 2.
|
|
5
|
+
description: "泛微 E10 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]-会议议题(查看/更新/删除)、议题类型、会议纪要查询、会议类型与应用设置、会议决议发布与决议任务跟踪。认证与会话由 weaver-e10-shared 提供。"
|
|
6
|
+
description_zh: "泛微 E10 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]-会议议题(查看/更新/删除)、议题类型、会议纪要查询、会议类型与应用设置、会议决议发布与决议任务跟踪。认证与会话由 weaver-e10-shared 提供。"
|
|
7
|
+
description_en: "Weaver E10 meeting management for daily meeting tasks: [Meetings] query today's / this week's / my meetings and meeting details, book meetings with automatic room & attendee conflict checks, reschedule / change location / change attendees, cancel, delete; [Rooms] query meeting rooms and occupancy / availability; [Attendees & receipts] view attendee lists, fill in / update meeting receipts; [Sign-in] view sign-in / sign-out status, manual sign-in, manual sign-out, clear sign-in records; [Meeting content] meeting agenda (view / update / delete), agenda types, minutes query, meeting types, application settings, meeting decision publishing and decision task tracking. Auth and session are provided by weaver-e10-shared."
|
|
8
|
+
version: 2.11.0
|
|
9
9
|
author: 泛微网络科技股份有限公司
|
|
10
10
|
agent_created: true
|
|
11
11
|
requires:
|
|
@@ -32,7 +32,8 @@ cliHelp: "weaver-work-cli meeting --help"
|
|
|
32
32
|
- 签到:「谁签到了」「签到率多少」「张三签到了吗」「帮他补签」「补签退」「清掉误签」
|
|
33
33
|
- 回执:「帮我回执不参加」「谁还没回执」「回执情况」
|
|
34
34
|
- 会议室/类型查询:「有哪些会议室」「会议室占用情况」「有哪些会议类型」
|
|
35
|
-
- EB 版独有(仅 mode=eb
|
|
35
|
+
- EB 版独有(仅 mode=eb):「会议议题」「议题类型」「会议纪要」「上会申请」「应用设置」「会议决议」「决议任务」
|
|
36
|
+
- **会议决议 / 决议任务**:「把这次会议的决议落到系统里」「生成会议任务」「这次会议定了哪些事要谁做」「我负责的会议任务完成了吗」→ 见「命令表(EB 版)」的决议区块(**用 `meeting.eb.decision.publish` 一步完成**)
|
|
36
37
|
- **不触发**(转对应 Skill):日程/日历类 → `weaver-e10-calendar`;审批流程发起 → `weaver-e10-workflow`;人员/部门查询 → `weaver-e10-hrm`。
|
|
37
38
|
|
|
38
39
|
## 认证与请求头契约
|
|
@@ -66,7 +67,7 @@ cliHelp: "weaver-work-cli meeting --help"
|
|
|
66
67
|
- 「补签 / 帮他补上签到」→ 先 `meeting.sign.list` 定位记录 → `meeting.sign.mark.prepare` → 确认后 `apply`;「补签退」用 `meeting.sign.back.prepare`。二者会改写签到事实,必须用户明确确认。
|
|
67
68
|
- 「清除签到记录 / 撤销签到」→ `meeting.sign.clear.prepare` → 确认后 `apply`。
|
|
68
69
|
- 「我要签到 / 扫码签到」→ **CLI 不支持**(依赖现场二维码票据 qrticket 与设备 IP),引导用户现场扫码或走 H5 签到;**绝不代签**。
|
|
69
|
-
- **EB 模式(mode=eb)下,同一意图改用 EB operations**(见「命令表(EB 版)」):今天的会议 → `meeting.eb.meeting.list`(先 count 再 list);建会 → 先读 `meeting.eb.baseSet.get` 判断是否需走流程,再 `meeting.eb.meeting.create.prepare`(**prepare 内部自动执行会议室/参会人冲突检测**:命中禁止提交 → `conflict_rejected` 直接停止;仅提醒 → 只写入 `warnings
|
|
70
|
+
- **EB 模式(mode=eb)下,同一意图改用 EB operations**(见「命令表(EB 版)」):今天的会议 → `meeting.eb.meeting.list`(先 count 再 list);建会 → 先读 `meeting.eb.baseSet.get` 判断是否需走流程,再 `meeting.eb.meeting.create.prepare`(**prepare 内部自动执行会议室/参会人冲突检测**:命中禁止提交 → `conflict_rejected` 直接停止;仅提醒 → 只写入 `warnings`,如实告知 + 建议换时间/换地点,是否继续由用户决定(不主动建议忽略冲突));变更/取消 → `meeting.eb.meeting.change.prepare`(取消用 `operate_type=CANCEL`、`mt_status=4`);签到 → `meeting.eb.sign.list`;回执 → `meeting.eb.receipt.list`;纪要 → `meeting.eb.minute.list`;议题 → `meeting.eb.topic.list`;**补签 → `meeting.eb.sign.mark`(走更新,禁止用 `sign.create`)**;**会议决议 → `meeting.eb.decision.publish`(写决议 + 自动把会议标为已决议,一步到位,勿拆成两步)**;决议任务 → `meeting.eb.decisionTask.list` / `.update`。
|
|
70
71
|
|
|
71
72
|
## 命令表(标准版,仅 mode=standard 使用)
|
|
72
73
|
|
|
@@ -89,6 +90,7 @@ cliHelp: "weaver-work-cli meeting --help"
|
|
|
89
90
|
| 签到记录与统计 | `meeting.sign.list` | [meeting-receipts-signs.md](references/meeting-receipts-signs.md) |
|
|
90
91
|
| 补签 / 补签退 | `meeting.sign.mark.prepare` → `apply` / `meeting.sign.back.prepare` → `apply` | [meeting-receipts-signs.md](references/meeting-receipts-signs.md) |
|
|
91
92
|
| 清除签到记录 | `meeting.sign.clear.prepare` → `apply` | [meeting-receipts-signs.md](references/meeting-receipts-signs.md) |
|
|
93
|
+
| 生成对象详情页链接(会议/会议室/签到…) | `meeting.link`(**禁止手工拼 URL**) | [meeting-eb.md](references/meeting-eb.md) |
|
|
92
94
|
|
|
93
95
|
## 命令表(EB 版,仅 mode=eb 使用)
|
|
94
96
|
|
|
@@ -111,21 +113,32 @@ cliHelp: "weaver-work-cli meeting --help"
|
|
|
111
113
|
| EB 议题类型列表 / 详情 / 更新 / 删除 | `meeting.eb.topicType.list` / `.detail` / `.update` / `.delete` | [meeting-eb.md](references/meeting-eb.md) |
|
|
112
114
|
| EB 会议议题列表 / 详情 / 更新 / 删除 | `meeting.eb.topic.list` / `.detail` / `.update` / `.delete` | [meeting-eb.md](references/meeting-eb.md) |
|
|
113
115
|
| EB 应用设置(判断审批/流程建会) | `meeting.eb.baseSet.get` | [meeting-eb.md](references/meeting-eb.md) |
|
|
116
|
+
| **EB 发布会议决议**(写决议 + 自动把会议标为已决议) | **`meeting.eb.decision.publish.prepare` → `apply`** | [meeting-eb.md](references/meeting-eb.md) |
|
|
117
|
+
| EB 决议列表 / 详情 / 删除 | `meeting.eb.decision.list` / `.detail` / `.delete.prepare` → `apply` | [meeting-eb.md](references/meeting-eb.md) |
|
|
118
|
+
| EB 决议任务列表(按会议 / 执行人 / 状态) | `meeting.eb.decisionTask.list` | [meeting-eb.md](references/meeting-eb.md) |
|
|
119
|
+
| EB 更新决议任务完成状态 | `meeting.eb.decisionTask.update.prepare` → `apply` | [meeting-eb.md](references/meeting-eb.md) |
|
|
120
|
+
| EB 补签 / 补签退(自动定位签到记录,走更新) | `meeting.eb.sign.mark.prepare` → `apply` | [meeting-eb.md](references/meeting-eb.md) |
|
|
114
121
|
| EB 会议室 / 参会人冲突检测(只查不建) | `meeting.eb.conflict.room` / `.member` | [meeting-eb.md](references/meeting-eb.md) |
|
|
122
|
+
| EB 详情页链接(会议/会议室/占用/签到/回执/纪要/议题/设置) | `meeting.link`(按 `type` 解析 objid 并缓存) | [meeting-eb.md](references/meeting-eb.md) |
|
|
123
|
+
| EB 传参口径(人员传 **HRM 员工 id**,非 E10 userId;日期 `yyyy-MM-dd HH:mm`) | — | [meeting-eb.md](references/meeting-eb.md) |
|
|
124
|
+
|
|
125
|
+
> **给验证方(其他智能体)**:需要"验证所有会议功能"时,直接交付 [meeting-verification.md](references/meeting-verification.md)
|
|
126
|
+
> —— 内含全部 **99 个 operation**(标准版 28 + EB 版 70 + 跨模式 1)的逐条判据、写链路模板、14 个负向用例、
|
|
127
|
+
> 8 项不在范围的功能清单,以及可直接粘贴的验证提示词与回执模板。
|
|
115
128
|
|
|
116
129
|
## 处理链:预约会议(高风险写,强制确认链)
|
|
117
130
|
|
|
118
131
|
1. **环境模式确定(强制第一步)**:跑 `meeting.env.detect`(默认读缓存,`refresh:true` 强制刷新);`mode=eb` → 本处理链改用 EB operations:先 `meeting.eb.baseSet.get` 读 `only_flow_create`/`open_mt_approve` 判断是否需走流程(需走 → 提示走 workflow,CLI 不直接建会),再 `meeting.eb.meeting.create.prepare`;`env_unavailable` → 提示并停止。
|
|
119
132
|
2. 解析意图:名称 / 时间(`yyyy-MM-dd HH:mm`)/ 会议室 / 参会人 / 类型;缺失字段先向用户确认,**禁止字段未确认直接建会**。
|
|
120
133
|
3. `meeting.room.list`(带时间范围)推荐空闲会议室、`meeting.type.list` 推荐类型,交用户点选。
|
|
121
|
-
3. `meeting.create.prepare`:自动执行建会路径检查(`onlyFlowCreate=1` 或周期工作流 → 拒绝并提示走流程)与**强制冲突检测**(接口失败视为冲突;`cansub=false` 直接拒绝;`cansub=true` 有冲突描述 → warnings
|
|
134
|
+
3. `meeting.create.prepare`:自动执行建会路径检查(`onlyFlowCreate=1` 或周期工作流 → 拒绝并提示走流程)与**强制冲突检测**(接口失败视为冲突;`cansub=false` 直接拒绝;`cansub=true` 有冲突描述 → warnings 如实告知并建议换时间/地点,**但不阻止提交**,是否继续由用户决定;CLI 不主动建议忽略冲突)。
|
|
122
135
|
4. 向用户摘要 preview(含 caller/contacter 默认当前用户、周期 `repeatDesc` 自动拼接)。
|
|
123
136
|
5. 用户明确确认 → `meeting.create.apply`(`confirm: true` + continuation,**10 分钟内有效,过期重新 prepare**)。
|
|
124
|
-
6. 结果回读用 `meeting.list
|
|
137
|
+
6. 结果回读用 `meeting.list`;会议链接一律用 `meeting.link` 生成(**禁止手工拼 URL**:EB 版 objid 需动态解析,标准版会议室/会议类型没有单页),再按 `[名称](url)` **内联到名称单元格**输出。
|
|
125
138
|
|
|
126
139
|
## 处理链:变更 / 取消 / 删除会议(高风险写,强制确认链)
|
|
127
140
|
|
|
128
|
-
1. 先用 `meeting.list`(或用户给出的会议 id
|
|
141
|
+
1. 先用 `meeting.list`(或用户给出的会议 id)定位目标会议,必要时用 `meeting.link` 生成 `[名称](url)` 给用户核对(EB 环境会议也在 `meeting.eb.meeting.list`)。
|
|
129
142
|
2. `meeting.change.prepare`(变更)/ `meeting.cancel.prepare` / `meeting.delete.prepare`(入参 `id`):CLI 先**回查会议存在性**;`change` 还会按会议状态自动决定 `operateType`(草稿=UPDATE、正常=CHANGE)。**权限判定交给服务端**,CLI 不做权限预判。成功返回 `preview` + `continuation`(10 分钟有效)。
|
|
130
143
|
3. 向用户摘要动作与后果(变更=覆盖所提交字段、当前状态与分流类型;取消=会议进入取消状态、占用/回执/签到失效;删除=**移入回收站**、不再出现在正常列表),**取得明确确认**。变更只提交 preview 中列出的字段。
|
|
131
144
|
4. 用户确认 → `meeting.*.apply`(`confirm: true` + continuation)。
|
|
@@ -136,6 +149,7 @@ cliHelp: "weaver-work-cli meeting --help"
|
|
|
136
149
|
|
|
137
150
|
- 最小必要查询;列表结果默认摘要输出,不粘贴超长 JSON。
|
|
138
151
|
- 长 id(`address`/`mtId`/`hrmMembers` 等)一律字符串传递(超 2^53 数字会失真)。
|
|
152
|
+
- **可点击链接一律内联到名称单元格**(`[会议名称](url)`),由 `meeting.link` 生成:**禁止**手工拼 URL,也**禁止**单列「详情链接」一行。
|
|
139
153
|
- **服务端业务/权限提示必须原文转达用户**:如"您没有变更权限!""您没有取消权限!""您没有删除权限!""您没有读取权限!"——不得改写成模糊说法,也不得建议用户绕过 CLI 自行拼接口。
|
|
140
154
|
- 错误驱动:`error.type/subtype` 决定下一步(`session_expired` → 登录流程;`conflict_rejected` → 停止;`confirmation.required` → 补 confirm;`meeting_not_operable` → 核对会议 id 与可见性)。
|
|
141
155
|
|
|
@@ -151,7 +165,9 @@ cliHelp: "weaver-work-cli meeting --help"
|
|
|
151
165
|
- 禁止绕过 CLI 直接 `curl`/`fetch` E10 接口;禁止读取认证目录与 Cookie。
|
|
152
166
|
- **历史变更**:旧 `meeting.update.*` 已下线(它固定发送 `operateType=UPDATE`,与业务语义不符),改由 `meeting.change.*` 按会议状态自动分流;迁移说明见 [meeting-update.md](references/meeting-update.md)。
|
|
153
167
|
- **提前结束会议(meeting.over)不提供**:服务端接口为空实现(源码中 service 调用被注释、直接返回成功),调用只会得到"假成功";请引导用户在系统页面操作,禁止绕过 CLI 自行拼接口。
|
|
154
|
-
- **EB 版已实现**(`meeting.eb
|
|
168
|
+
- **EB 版已实现**(`meeting.eb.*`,覆盖会议/会议室/占用/签到/回执/纪要/会议类型/议题类型/会议议题/应用设置/**会议决议与决议任务**的全部 interfacePk):`mode=eb` 时必须使用它们,**不要**再回答"EB 版未实现"。注意**标准版会议本身没有议题/纪要能力**(EB 版才有)——若当前是 standard 模式而用户问议题/纪要,如实说明属 EB 版能力(EB 环境下才可查)。
|
|
169
|
+
- **⚠️ EB 返回值解析(真机踩坑,必看)**:EB 接口里**人员 / 关联 / 状态 / 选择类字段返回的是「对象数组」**(如 `[{"name":"崔智友","id":"1305319807700025376"}]`),**不是字符串** —— 取 id 读 `[0].id`、显示名读 `[0].name`;**禁止**把整个数组当字符串拼接(会显示成 `System.Object[]`,看起来像"字段为空")。已实测这类形态:`executors` / `rummager` / `mt_id` / `relate_decision` / `decision_status` / `detail1[].executor`;而 `finish_status`("未完成"/"已完成")、日期、`id` 是字符串。另注意 **`sign.list` 返回的 `mt_id` 是「会议室/会议名称」而筛选参数 `mt_id` 才是会议 id**。
|
|
170
|
+
- **⚠️ EB 写操作失败语义(真机踩坑,必看)**:**EB 写接口"报错"≠"没有写入"** —— 实测 `create_mt_decision` 因字段校验失败返回错误时,主表与明细其实**已经落库**,重试产生了重复数据。因此写操作失败后**必须先回查**(list/detail)再决定重试或删除;CLI 的语义化写操作(`decision.publish`、`sign.mark`)在拿不到明确成功证据时会抛 **`partial`** 并提示"先回查、勿盲目重试",请如实转达用户。
|
|
155
171
|
- EB 版纪要**创建**未实现(该功能走 OpenAPI、无自定义 interfacePk),仅支持 `meeting.eb.minute.list` 查询。
|
|
156
172
|
- 周期会议走工作流(`repeatWorkFlowBaseIds` 非空)与 `onlyFlowCreate=1` 的流程建会需要工作流集成,CLI 会主动拒绝。
|
|
157
173
|
- **冲突处置严格按应用设置,CLI 不额外加码**:`=1`「禁止提交」→ 怎么都不允许提交(抛 `conflict_rejected`,不询问、不新建);`=0`「仅提醒」→ 如实告知冲突并建议换时间/换地点,**但不阻止提交** —— 用户了解冲突后仍要求新建时,可继续 `apply` 完成建会。**检测失败 → `conflict_check_failed` 拒绝**(策略无法判定时保守处理)。
|