weaver-work-cli 0.1.21 → 0.1.23

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.
Files changed (31) hide show
  1. package/dist/shortcuts/invoice/host.js +5 -4
  2. package/dist/shortcuts/meeting/operations/change.js +7 -4
  3. package/dist/shortcuts/meeting/operations/create.js +5 -3
  4. package/dist/shortcuts/meeting/operations/eb/association.js +151 -0
  5. package/dist/shortcuts/meeting/operations/eb/attendance-summary.js +153 -0
  6. package/dist/shortcuts/meeting/operations/eb/catalog.js +185 -13
  7. package/dist/shortcuts/meeting/operations/eb/conflict.js +17 -11
  8. package/dist/shortcuts/meeting/operations/eb/decision.js +405 -0
  9. package/dist/shortcuts/meeting/operations/eb/factory.js +222 -35
  10. package/dist/shortcuts/meeting/operations/eb/index.js +18 -1
  11. package/dist/shortcuts/meeting/operations/eb/link.js +212 -0
  12. package/dist/shortcuts/meeting/operations/eb/task-progress.js +123 -0
  13. package/dist/shortcuts/meeting/operations/env.js +78 -61
  14. package/dist/shortcuts/meeting/operations/lifecycle.js +8 -6
  15. package/dist/shortcuts/meeting/operations/link.js +125 -0
  16. package/dist/shortcuts/meeting/operations/receipts.js +54 -21
  17. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  18. package/dist/shortcuts/meeting/operations/shared.js +41 -0
  19. package/dist/shortcuts/meeting/operations/signs.js +14 -4
  20. package/dist/shortcuts/project/operations/read.js +7 -6
  21. package/docs/e10-auth.md +4 -0
  22. package/docs/invoice.md +3 -0
  23. package/package.json +1 -1
  24. package/skills/weaver-e10-meeting/SKILL.md +28 -11
  25. package/skills/weaver-e10-meeting/references/meeting-eb.md +172 -7
  26. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +7 -2
  27. package/skills/weaver-e10-meeting/references/meeting-verification.md +271 -0
  28. package/skills/weaver-e10-meeting.zip +0 -0
  29. package/skills/weaver-e10-shijingran/SKILL.md +68 -3
  30. package/skills/weaver-e10-shijingran/references/disabled-capabilities.md +22 -0
  31. package/skills/weaver-e10-shijingran/references/source-manifest.json +12 -4
@@ -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)) {
@@ -48,6 +61,34 @@ export function toIdString(value, label) {
48
61
  return value;
49
62
  throw meetingValidationError('id_invalid', `${label} 必须是数字 id(超长 id 用字符串)`);
50
63
  }
64
+ /** 取 apply 入参里的业务字段(排除 confirm/continuation)。 */
65
+ export function pickBusinessInput(args) {
66
+ const out = {};
67
+ for (const [key, value] of Object.entries(args)) {
68
+ if (key === 'confirm' || key === 'continuation')
69
+ continue;
70
+ if (value === undefined)
71
+ continue;
72
+ out[key] = value;
73
+ }
74
+ return out;
75
+ }
76
+ /**
77
+ * 标准版 apply 的入参合并:**以 prepare 快照为基**,入参里的业务字段作为覆盖。
78
+ *
79
+ * 为什么需要:prepare 已经把「用户将要确认的内容」规范化后存进 continuation,而各 apply 的
80
+ * 入参 schema 又要求回传 name/beginDatetime/endDatetime 等业务字段 —— 文档与调用方习惯都是
81
+ * 「apply 只传 confirm + continuation」,于是出现「文档说只传两个参数,实际报 name 必填」的矛盾
82
+ * (2026-09-23、2026-10-08 两次真机踩到)。
83
+ *
84
+ * 语义:只传 confirm+continuation → 直接用快照(即预览过的内容);
85
+ * 传入了不同值 → 合并后由 fingerprint 校验报 `target_changed`(提示重新 prepare),
86
+ * 因此不会绕过「确认过的内容」这道闸门。
87
+ */
88
+ export function mergeSnapshot(snapshot, args) {
89
+ const base = snapshot && typeof snapshot === 'object' ? { ...snapshot } : {};
90
+ return { ...base, ...pickBusinessInput(args) };
91
+ }
51
92
  /** 逗号分隔 id 串:接受数组或字符串,输出字符串。 */
52
93
  export function toIdCsv(value, label) {
53
94
  if (value === undefined || value === null || value === '')
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { meetingConfirmationError, meetingPartialError, meetingValidationError } from '../errors.js';
3
3
  import { meetingSuccess, SCHEMA_ID } from './types.js';
4
- import { extractRows, toIdString } from './shared.js';
4
+ import { extractRows, mergeSnapshot, pickBusinessInput, toIdString } from './shared.js';
5
5
  const CONTINUATION_TTL_SECONDS = 600;
6
6
  function fingerprint(value) {
7
7
  return createHash('sha256').update(JSON.stringify(value ?? null)).digest('hex').slice(0, 32);
@@ -212,13 +212,23 @@ function signWritePair(spec) {
212
212
  continuation: { type: 'string', minLength: 1 },
213
213
  ...inputProps,
214
214
  },
215
- required: ['confirm', 'continuation', 'id'],
215
+ required: ['confirm', 'continuation'],
216
216
  },
217
217
  handler: async (args, env) => {
218
218
  requireConfirm(args, applyName);
219
219
  const lease = await verifyLease(args, applyName, env);
220
- const target = await resolveSignTarget(args, env, applyName);
221
- const payload = { id: target.signRecordId, meetingid: target.meetingId };
220
+ // 只传 confirm+continuation 时用 prepare 快照(快照里已含解析好的签到记录 id 与会议 id);
221
+ // 传了 id/mtId 才重新回查定位并校验一致性。
222
+ const business = pickBusinessInput(args);
223
+ let payload;
224
+ if (Object.keys(business).length === 0) {
225
+ const snapshot = lease.payload;
226
+ payload = { id: String(snapshot.id ?? ''), meetingid: String(snapshot.meetingid ?? '') };
227
+ }
228
+ else {
229
+ const target = await resolveSignTarget(mergeSnapshot(lease.payload, args), env, applyName);
230
+ payload = { id: target.signRecordId, meetingid: target.meetingId };
231
+ }
222
232
  if (lease.fingerprint !== fingerprint(payload)) {
223
233
  throw meetingValidationError('target_changed', '签到记录或会议与 prepare 时的快照不一致,请重新执行 prepare');
224
234
  }
@@ -1,4 +1,5 @@
1
1
  import { assertDatajsonSuccess, assertWeaverSuccess, } from '../host.js';
2
+ import { projectValidationError } from '../errors.js';
2
3
  import { SCHEMA_FILTERS, SCHEMA_FIELDS, SCHEMA_ID_STRING, SCHEMA_PAGE_NO, SCHEMA_PAGE_SIZE, projectSuccess, schemaOf, } from './types.js';
3
4
  import { compactFilters, idString, operatorId, positiveInt, queryText, } from './shared.js';
4
5
  const APP_TAG = 'weaver-project-eb';
@@ -36,12 +37,12 @@ function outputFields(value) {
36
37
  if (value === undefined)
37
38
  return null;
38
39
  if (!Array.isArray(value) || value.length === 0) {
39
- throw new Error('fields 必须是非空字符串数组');
40
+ throw projectValidationError('field_invalid', 'fields 必须是非空字符串数组');
40
41
  }
41
42
  const fields = [];
42
43
  for (const item of value) {
43
44
  if (typeof item !== 'string' || !/^[A-Za-z]/.test(item) || /[^A-Za-z0-9_]/.test(item)) {
44
- throw new Error('fields 包含非法字段名');
45
+ throw projectValidationError('field_invalid', 'fields 包含非法字段名');
45
46
  }
46
47
  fields.push(item);
47
48
  }
@@ -65,12 +66,12 @@ function normalizeFieldNames(value, inputName) {
65
66
  if (value === undefined)
66
67
  return [];
67
68
  if (!Array.isArray(value) || value.length === 0) {
68
- throw new Error(`${inputName} 必须是非空字符串数组`);
69
+ throw projectValidationError('field_invalid', `${inputName} 必须是非空字符串数组`);
69
70
  }
70
71
  const names = [];
71
72
  for (const item of value) {
72
73
  if (typeof item !== 'string' || item.trim() === '' || item.length > 100) {
73
- throw new Error(`${inputName} 包含非法字段名`);
74
+ throw projectValidationError('field_invalid', `${inputName} 包含非法字段名`);
74
75
  }
75
76
  names.push(item);
76
77
  }
@@ -130,7 +131,7 @@ async function decorateDetailUrls(host, operation, datajson) {
130
131
  }
131
132
  function table(value) {
132
133
  if (value !== 'task' && value !== 'project') {
133
- throw new Error('table must be task or project');
134
+ throw projectValidationError('field_invalid', 'table 必须是 task 或 project');
134
135
  }
135
136
  return value;
136
137
  }
@@ -209,7 +210,7 @@ function listOperation(name, path, nameField, descField, statusField) {
209
210
  await resolveCurrentUserFilters(filters, host);
210
211
  const allStatuses = args.allStatuses === true;
211
212
  if (allStatuses && (filters.sys_status !== undefined || filters[statusField] !== undefined)) {
212
- throw new Error("allStatuses 不能与 sys_status 或业务状态同时传");
213
+ throw projectValidationError('field_conflict', 'allStatuses 不能与 sys_status 或业务状态同时传');
213
214
  }
214
215
  if (allStatuses) {
215
216
  delete filters.sys_status;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weaver-work-cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "面向泛微工作工具链的可扩展 TypeScript CLI 基础框架(E10 业票通等能力)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]-会议议题(查看/更新/删除)、议题类型、会议纪要查询、会议类型与应用设置。认证与会话由 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 and application settings. Auth and session are provided by weaver-e10-shared."
8
- version: 2.6.0
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.13.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`,建议换时间/换地点,不提供绕过选项);变更/取消 → `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`。
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,24 +113,36 @@ 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 如实告知并建议换时间/地点,**但不阻止提交**:用户了解冲突后仍要求新建时可继续 `apply`)。
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`,会议链接由 Agent 按 `{baseUrl}/sp/meeting/meetingSingle?meetingId={id}` 内联输出。
137
+ **apply 的入参口径(2026-10-08 修正)**:只传 `confirm` + `continuation` 即可 —— 业务字段以 prepare 快照为准(快照就是用户刚确认过的内容);若同时传了业务字段,则必须与快照一致,否则报 `target_changed`(提示重新 prepare),**不会**绕过确认闸门。
138
+ 6. 结果回读用 `meeting.list`;会议链接一律用 `meeting.link` 生成(**禁止手工拼 URL**:EB 版 objid 需动态解析,标准版会议室/会议类型没有单页),再按 `[名称](url)` **内联到名称单元格**输出。
125
139
 
126
140
  ## 处理链:变更 / 取消 / 删除会议(高风险写,强制确认链)
127
141
 
128
- 1. 先用 `meeting.list`(或用户给出的会议 id)定位目标会议,必要时把 `[名称]({baseUrl}/sp/meeting/meetingSingle?meetingId={id})` 给用户核对。
142
+ 1. 先用 `meeting.list`(或用户给出的会议 id)定位目标会议,必要时用 `meeting.link` 生成 `[名称](url)` 给用户核对(EB 环境会议也在 `meeting.eb.meeting.list`)。
129
143
  2. `meeting.change.prepare`(变更)/ `meeting.cancel.prepare` / `meeting.delete.prepare`(入参 `id`):CLI 先**回查会议存在性**;`change` 还会按会议状态自动决定 `operateType`(草稿=UPDATE、正常=CHANGE)。**权限判定交给服务端**,CLI 不做权限预判。成功返回 `preview` + `continuation`(10 分钟有效)。
130
144
  3. 向用户摘要动作与后果(变更=覆盖所提交字段、当前状态与分流类型;取消=会议进入取消状态、占用/回执/签到失效;删除=**移入回收站**、不再出现在正常列表),**取得明确确认**。变更只提交 preview 中列出的字段。
131
- 4. 用户确认 → `meeting.*.apply`(`confirm: true` + continuation)。
145
+ 4. 用户确认 → `meeting.*.apply`(`confirm: true` + continuation;**只传这两个参数即可**,业务字段取 prepare 快照;改动内容会报 `target_changed`)。
132
146
  5. 结果回读:`meeting.list` 确认状态(取消/删除后该会议不应再出现在正常列表)。
133
147
  6. **禁止**跳过 prepare 直接 apply;**禁止**在未确认目标存在时执行写操作。
134
148
 
@@ -136,6 +150,7 @@ cliHelp: "weaver-work-cli meeting --help"
136
150
 
137
151
  - 最小必要查询;列表结果默认摘要输出,不粘贴超长 JSON。
138
152
  - 长 id(`address`/`mtId`/`hrmMembers` 等)一律字符串传递(超 2^53 数字会失真)。
153
+ - **可点击链接一律内联到名称单元格**(`[会议名称](url)`),由 `meeting.link` 生成:**禁止**手工拼 URL,也**禁止**单列「详情链接」一行。
139
154
  - **服务端业务/权限提示必须原文转达用户**:如"您没有变更权限!""您没有取消权限!""您没有删除权限!""您没有读取权限!"——不得改写成模糊说法,也不得建议用户绕过 CLI 自行拼接口。
140
155
  - 错误驱动:`error.type/subtype` 决定下一步(`session_expired` → 登录流程;`conflict_rejected` → 停止;`confirmation.required` → 补 confirm;`meeting_not_operable` → 核对会议 id 与可见性)。
141
156
 
@@ -151,7 +166,9 @@ cliHelp: "weaver-work-cli meeting --help"
151
166
  - 禁止绕过 CLI 直接 `curl`/`fetch` E10 接口;禁止读取认证目录与 Cookie。
152
167
  - **历史变更**:旧 `meeting.update.*` 已下线(它固定发送 `operateType=UPDATE`,与业务语义不符),改由 `meeting.change.*` 按会议状态自动分流;迁移说明见 [meeting-update.md](references/meeting-update.md)。
153
168
  - **提前结束会议(meeting.over)不提供**:服务端接口为空实现(源码中 service 调用被注释、直接返回成功),调用只会得到"假成功";请引导用户在系统页面操作,禁止绕过 CLI 自行拼接口。
154
- - **EB 版已实现**(`meeting.eb.*`,覆盖会议/会议室/占用/签到/回执/纪要/会议类型/议题类型/会议议题/应用设置的全部 interfacePk):`mode=eb` 时必须使用它们,**不要**再回答"EB 版未实现"。注意**标准版会议本身没有议题/纪要能力**(EB 版才有)——若当前是 standard 模式而用户问议题/纪要,如实说明属 EB 版能力(EB 环境下才可查)。
169
+ - **EB 版已实现**(`meeting.eb.*`,覆盖会议/会议室/占用/签到/回执/纪要/会议类型/议题类型/会议议题/应用设置/**会议决议与决议任务**的全部 interfacePk):`mode=eb` 时必须使用它们,**不要**再回答"EB 版未实现"。注意**标准版会议本身没有议题/纪要能力**(EB 版才有)——若当前是 standard 模式而用户问议题/纪要,如实说明属 EB 版能力(EB 环境下才可查)。
170
+ - **⚠️ 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**。
171
+ - **⚠️ EB 写操作失败语义(真机踩坑,必看)**:**EB 写接口"报错"≠"没有写入"** —— 实测 `create_mt_decision` 因字段校验失败返回错误时,主表与明细其实**已经落库**,重试产生了重复数据。因此写操作失败后**必须先回查**(list/detail)再决定重试或删除;CLI 的语义化写操作(`decision.publish`、`sign.mark`)在拿不到明确成功证据时会抛 **`partial`** 并提示"先回查、勿盲目重试",请如实转达用户。
155
172
  - EB 版纪要**创建**未实现(该功能走 OpenAPI、无自定义 interfacePk),仅支持 `meeting.eb.minute.list` 查询。
156
173
  - 周期会议走工作流(`repeatWorkFlowBaseIds` 非空)与 `onlyFlowCreate=1` 的流程建会需要工作流集成,CLI 会主动拒绝。
157
174
  - **冲突处置严格按应用设置,CLI 不额外加码**:`=1`「禁止提交」→ 怎么都不允许提交(抛 `conflict_rejected`,不询问、不新建);`=0`「仅提醒」→ 如实告知冲突并建议换时间/换地点,**但不阻止提交** —— 用户了解冲突后仍要求新建时,可继续 `apply` 完成建会。**检测失败 → `conflict_check_failed` 拒绝**(策略无法判定时保守处理)。
@@ -17,9 +17,12 @@
17
17
  | 列表/总数 | 筛选条件走 **URL query**(参数名为 eBuilder 配置的占位符名,如 `hrmmember`、`mt_status`,**不是表 dataKey**);body 必须带 `datajson`;CLI 先 count 再 list(同条件),`total` 来自 count |
18
18
  | 详情 | `getFormDataByPk`,body 顶层 `mainTable.id` |
19
19
  | 写操作 | body 用 **`datas` 数组**包裹 `mainTable`(缺 `datas` 会报"datas节点未找到"),`header.operator` 为当前用户 |
20
- | 日期字段 | 一律 `yyyy-MM-dd HH:mm`(**不带秒**,带秒报"不满足传入格式") |
21
- | 人员 id | 传**员工 id**(不是姓名、不是 userid 以外的其它口径);姓名→id 用 `weaver-e10-hrm` |
20
+ | 日期字段(**两种格式并存,务必看清**) | **① 多数不带秒 `yyyy-MM-dd HH:mm`**:会议 `begin_datetime` / `end_datetime`、决议明细 `begin_datetime` / `end_datetime`;**② 少数必须带秒 `yyyy-MM-dd HH:mm:ss`**:决议任务 `finish_date`、签到 `sign_time` / `signback_time` / `allow_late_time`。传错会报「传入字段:xxx…不满足传入格式」(2026-09-21 真机实测)|
21
+ | **写/改操作的主键** | **update 的主键必须走 URL**(形如 `POST /updateFormData/{interfacePk}?id=<主键>`):只放在 body 的 `datas[].mainTable.id` 会被拒,报「**主键传入不能为空**」(2026-09-21 真机实测 `update_meeting_sign_info`)。CLI 已按接口声明自动携带,body 里同时保留 `id` 以满足文档「id 必填」。|
22
+ | 人员 id(**分场景,勿混**) | **决议 / 冲突检测 / 会议人员 → HRM 员工 id**(形如 `1305319992400396336`):`executors`、`rummager`、`hrm_members`、`signMember` 筛选用;**签到表的 `user_id` → 实测存的是 E10 userId**(形如 `6788539829119521816`),补签匹配按它比对。姓名→员工 id 用 `weaver-e10-hrm`。⚠️ 传错**不报错**,但会静默查不到 / 冲突漏检 |
22
23
  | 新建/更新 | create 不传 id(系统生成);update/delete 必传 id |
24
+ | **⚠️ 返回结构(必读)** | **人员 / 关联 / 状态 / 选择类字段返回的是「对象数组」,不是字符串**,形如 `[{"name":"崔智友","id":"1305319807700025376"}]` —— 取 id 读 `[0].id`、取显示名读 `[0].name`;**禁止**把整个数组直接当字符串拼接(会显示成 `System.Object[]`,看起来像"字段为空")。已实测这类字段:`executors` / `rummager` / `mt_id` / `relate_decision` / `decision_status` / `detail1[].executor` / `detail1[].finish_status`;而 `finish_date` / `id` / 日期 / 普通文本字段是字符串 |
25
+ | **⚠️ 写操作失败语义(必读)** | **EB 写接口"报错"≠"没有写入"**:实测 `create_mt_decision` 因字段校验失败(传了不被接受的 `data_status`)返回错误,但主表与明细**已经落库**,重试后产生了**重复决议 + 重复任务**。因此:写操作失败后**必须先回查**(`list` / `detail`)再决定重试或删除;CLI 的语义化写操作(`decision.publish` 等)在拿不到明确成功证据时会抛 **`partial`** 而非普通失败,并按此提示调用方 |
23
26
 
24
27
  ## operation 清单
25
28
 
@@ -36,6 +39,14 @@
36
39
  | 会议议题 9.x | `meeting.eb.topic.list` / `.detail` | `meeting.eb.topic.update` / `.delete` | query_mt_topic_list(+count) / query_mt_topic_info / update_mt_topic_info / delete_mt_topic_info |
37
40
  | 应用设置 7.x | `meeting.eb.baseSet.get` | — | query_meeting_baseset |
38
41
  | 冲突检测 | `meeting.eb.conflict.room` / `.member`(只读) | — | query_roomusage_list / query_mtDetail_list + query_meeting_receipt_list(+count) |
42
+ | **决议概述** | `meeting.eb.decision.list` / `.detail` | `meeting.eb.decision.create` / `.delete` / **`.publish`(推荐)** | query_mt_decision_list(+count) / query_mt_decision_info / create_mt_decision / delete_mt_decision / update_mt_isdecision |
43
+ | **决议任务** | `meeting.eb.decisionTask.list` | `meeting.eb.decisionTask.update` | query_mt_decision_task_list(+count) / update_mt_decision_task |
44
+ | **补签(语义化)** | — | `meeting.eb.sign.mark` | update_meeting_sign_info(自动定位签到记录 id)|
45
+
46
+ > ⚠️ **补签务必带时间(或让 CLI 补当前时间)**:2026-10-08 真机实测,只传 `mt_id` + `user_id` 时 update 里没有任何
47
+ > 时间字段,**服务端照样返回成功、CLI 也报 COMPLETE,但签到记录一点没变**(静默无效)。现 CLI 已兜底:
48
+ > 未传 `sign_time` / `signback_time` / `sign_out_status` 时按"补签"语义**自动补当前时间**(带秒)并写入 `warnings`,
49
+ > 实际写入值在 `preview.after` 里可见;要指定时间请显式传 `sign_time`(`yyyy-MM-dd HH:mm:ss`,带秒)。
39
50
 
40
51
  ## 常用命令
41
52
 
@@ -66,24 +77,109 @@ weaver-work-cli --json meeting run meeting.eb.meeting.create.prepare --input-jso
66
77
  - write prepare:`{ status: 'AWAITING_CONFIRMATION', preview: { interfacePk, kind, body, path, notes }, continuation }`——**先向用户摘要 body 内容再确认**。
67
78
  - write apply:`{ status: 'COMPLETE', interfacePk, dataIds }`;回读用对应 list/detail 核对。
68
79
 
80
+ ## 会议决议与决议任务(EB 版,2026-09-21 新增)
81
+
82
+ **用途**:把会议成果(结合会议议题、纪要、用户提供的文档/信息)**汇总成决议**,系统按决议明细**自动生成待办任务**,后续可跟踪完成情况。
83
+
84
+ ### 一句话链路
85
+
86
+ ```
87
+ ① meeting.eb.decision.publish ← 推荐入口:写「决议概述 + 明细」,并自动把会议标为已决议
88
+ ├ 内部第 1 步 create_mt_decision 写概述(mt_content)+ 明细(detail1,每条 = 一项待办)
89
+ └ 内部第 2 步 update_mt_isdecision 写回会议:decision_Id + is_decision=2 + mt_status=8
90
+ ② (用户配置的 ESB 联动)按决议明细生成「决议任务」数据 —— CLI 不参与、不写任务表
91
+ ③ meeting.eb.decisionTask.list 查任务(按会议 / 执行人 / 状态)
92
+ ④ meeting.eb.decisionTask.update 按执行人更新任务完成状态
93
+ ```
94
+
95
+ ### 关键字段口径(务必按此传)
96
+
97
+ - **`mt_content`**:决议概述文本(一段话),由调用方汇总后传入,CLI 不生成内容。
98
+ - **不要传 `data_status`**:`create_mt_decision` **不接受**该字段(真机报 `DataError:dataKey:data_status,该字段不存在`),提交状态由系统自动置 `1`(回查列表 `data_status=1`,ESB 联动照常触发)。
99
+ - **`detail1`(决议明细,至少 1 条)**:
100
+ | 字段 | 必填 | 说明 |
101
+ |---|---|---|
102
+ | `decision_title` | ✅ | 决议标题(= 任务标题)|
103
+ | `executors` | ✅ | 执行人,**HRM 员工 id**(多人逗号分隔)|
104
+ | `begin_datetime` / `end_datetime` | ✅ | `yyyy-MM-dd HH:mm` |
105
+ | `decision_status` | ✅ | 0未完成 / 1已完成 / 2已延期(默认 0)|
106
+ | `rummager` | ⭕ | 检查人,**员工 id**(**非必填**)|
107
+ | `related_type` | ⭕ | 0未关联 / 1日程 / 2任务(默认 0)|
108
+ | `sort_number` | ⭕ | 明细序号,省略时按数组顺序自动编号 |
109
+ - **`data_status`**:创建时 CLI **固定传 `1`(提交)** —— 传 0(暂存)**不会触发 ESB 联动,决议任务永远不会生成**。
110
+ - **会议侧**:`is_decision=2`(决议提交完成)+ `decision_Id=<决议概述 id>` + `mt_status=8`(结束)。**产生决议后会议即视为结束**,这是服务端口径(服务端源码 `decisionStatus==2` 时 `isover=true`)。
111
+ - **任务侧**:`mainTable` = 一条任务(含 `decision_title` / `executors` / `decision_status` / `mt_id` / `relate_decision`);`detail1` = 各执行人的 `finish_status`(0未完成/1已完成/2已延期)+ `finish_date`。
112
+
113
+ ### 返回结构(解析前必读,2026-09-21 真机实测)
114
+
115
+ `decision.list` / `decisionTask.list` / `decision.detail` 中,**人员、关联、状态类字段返回的是「对象数组」而不是字符串**:
116
+
117
+ ```json
118
+ "executors": [{ "name": "崔智友", "id": "1305319807700025376" }]
119
+ "rummager": [{ "name": "裴永俊", "id": "1305319812204708008" }]
120
+ "mt_id": [{ "name": "你好中国", "id": "1316455906280819022" }]
121
+ "relate_decision": [{ "name": "会议决议概述表-…", "id": "1316456666740138910" }]
122
+ "decision_status": [{ "name": "未完成", "id": "0" }]
123
+ ```
124
+
125
+ ⇒ 取 id 读 `[0].id`、取显示名读 `[0].name`;**不要**把整个数组直接当字符串拼进回复(会显示成 `System.Object[]`)。
126
+ `detail1[]` 里 **`executor` 是对象数组**,而 **`finish_status` 是字符串**(形如 `"未完成"` / `"已完成"`),`finish_date` / `id` 也是字符串。
127
+ ⚠️ **`decisionTask.list` 的 `detail1` 并非每行都返回**(真机实测:同一批任务里只有部分行带 `detail1`)—— 需要逐个执行人的完成状态时,不要假设每条都有 `detail1`;主表 `decision_status` 与 `detail1[].finish_status` 是两套状态(**主表归检查人维护,CLI 只更新 `detail1`**)。
128
+
129
+ **筛选参数(已真机验证生效)**:
130
+ - `decision.list` / `decisionTask.list`:`mt_id` = **会议 id**(不是决议 id、不是任务 id)。用无关会议 id 查询返回 0 条、用真实会议 id 返回正确条数 ⇒ 参数确实生效。
131
+ - `decisionTask.list` 另支持 `relate_decision`(决议概述 id)、`executors`(员工 id)、`decision_status`。
132
+ - 实测样例:「你好中国」(`mt_id=1316455906280819022`)→ 决议 1 条 + 决议任务 2 条(「解决方案」「研究方案」,执行人崔智友、检查人裴永俊);**证明 ESB 联动确实按决议明细生成了任务**。
133
+
134
+ ### 必须遵守的行为
135
+
136
+ 1. **优先用 `meeting.eb.decision.publish`**,不要手写"create 完再 markDecided"两步 —— 漏第二步会造成「决议建了、会议却还显示未决议」的半成品状态。
137
+ 2. **一次会议只有一个决议概述**:重复 publish 前先 `decision.list`(按 `mt_id`)确认是否已有。
138
+ 3. **明细至少 1 条**:没有明细等于没有任务,CLI 直接拒绝(`missing_required_field`)。
139
+ 4. **`publish` 是写操作**:必须先 `prepare` 给用户看预览(概述 + 明细逐条),用户确认后才 `apply`。
140
+ 5. **部分失败必须如实转达**:
141
+ - 决议已建但**写回会议失败** → 抛 `partial`,消息含 `decision_Id` —— **不要重复创建决议**,可稍后用 `meeting.eb.meeting.markDecided` 补标记(正文里说明了这点)。
142
+ - 决议可能已写入但**没返回 `dataIds`** → 抛 `partial`,提示先用 `decision.list(mt_id)` 回查,**同样不要重复创建**。
143
+ 6. **任务与进度的读法(真机实测口径)**:
144
+ - **一条决议明细 = 一个任务**;明细里写多个执行人 ⇒ 该任务**多人共同跟进**(实测:1 明细 + 2 执行人 → 只生成 1 个任务)。
145
+ - ⚠️ **2026-10-08 复测出现「1 明细 + 1 执行人 → 生成 2 条任务」**:任务条数由**用户配置的 ESB 联动**决定,
146
+ 可能随模板/配置变化,**不要用「明细条数 = 任务数」推算**,一律以 `decisionTask.list` 的实际返回为准。
147
+ - **更新**用 `meeting.eb.decisionTask.update`:按 `executor` 写 `detail1`(`finish_status` 未完成/已完成/已延期 + `finish_date` **带秒**)。
148
+ - ⚠️ **`markDecided` 的必填组合(2026-10-08 实测)**:`is_decision=2`(决议提交完成)时**必须同时传 `decision_Id` 与 `mt_status`**,
149
+ 否则 `prepare` 能通过、`apply` 被服务端拒绝:原文 `DataError:存为未填写的必填字段: decision_Id,mt_status ,请确认`。
150
+ 正确样例:`{"id":"<会议id>","decision_Id":"<决议概述id>","is_decision":"2","mt_status":"8"}`(回读会议:`mt_status=结束`)。
151
+ **`finish_date` 是服务端必填**;把任务改回「未完成」时传一个**空格**即可显式清空。
152
+ **同一执行人重复更新 = 覆盖该人记录,不同执行人 = 追加**(实测两人跟进后 detail1 保留两条,互不覆盖)。
153
+ - ★ **读进度直接用 `decisionTask.list` 返回的 `data.progress`**(不必自己解析 detail1):
154
+ `progress.summary` = 整页汇总(`total` / `done` / `overdue` / `inProgress` / `pending`);
155
+ `progress.items[].progress` = 逐任务 `{ total, done, overdue, pending, status, members[] }`,
156
+ 其中 `status ∈ done 全员完成 | overdue 有人延期 | inProgress 部分完成 | pending 尚未开始`,
157
+ `members[]` 给出每人 `executorId` / `executorName` / `status` / `finishDate`。
158
+ ⚠️ 统计以 `executors` 为人员基准:**未跟进的执行人也会计入 `total` 并算作 `pending`**(不会漏人)。
159
+ - 原始信息仍可从 `rows[].detail1`(每人 `finish_status` / `finish_date` / `executor`)读取;`rows` **原样返回、未被改写**。
160
+ ⚠️ **`mainTable.decision_status` 不可作为进度依据**:实测**始终为「未完成」**,既不随 detail1 变化,也无法经 `update_mt_decision_task` 写入(该接口只映射 detail1)。
161
+ - 查询:`meeting.eb.decisionTask.list`(按 `mt_id` 查某会议全部任务 / 按 `executors` 传员工 id 查某人的任务 / 分页 `pageNo`+`pageSize`)。
162
+ 7. **`executors` / `rummager` / `user_id` 一律传 HRM 员工 id**(不是 E10 userId)—— 传错不报错但会静默失效。
163
+
69
164
  ## 冲突检测(EB 版,强制)
70
165
 
71
166
  > 2026-09-20 补齐:此前 EB 版建会**没有任何冲突校验** —— 重复占用同一间会议室、参会人在同一时段撞会都不会被拦住。
72
167
 
73
168
  - **自动强制(建会/变更自带)**:`meeting.eb.meeting.create.prepare` 与 `meeting.eb.meeting.change.prepare` 在生成确认预览**之前**自动执行会议室与参会人冲突检测(传了 `address` / `hrm_members` 才会真的发起查询)。命中时按应用设置处置:
74
169
  - `room_conflict=1` / `member_conflict=1`(禁止提交)→ 抛 `conflict_rejected`:**直接停止,不询问、不新建**;
75
- - `room_conflict=0` / `member_conflict=0`(仅提醒)→ 写入 `warnings`:如实告知冲突并建议换时间/换地点,**但不阻止提交** —— 用户了解冲突后仍要求新建时,可继续 `apply` 完成建会;
170
+ - `room_conflict=0` / `member_conflict=0`(仅提醒)→ 写入 `warnings`:**如实告知冲突 + 建议换时间/换地点 + 决定权交用户**;CLI 与服务端都不阻止提交,但 CLI **不主动建议忽略冲突**,也不提供"忽略 / 强制"类选项;
76
171
  - 检测请求本身失败 → 抛 `conflict_check_failed`(fail-closed:宁可拦住,不可漏检)。
77
172
  - **只查不建**:`meeting.eb.conflict.room` / `meeting.eb.conflict.member`,返回 `verdict`:`ok` 无冲突 / `warn` 仅提醒 / `rejected` 禁止提交 / `skipped` 应用设置已关闭该项检测。
78
173
  - **口径(与 standalone skill 一致,不可擅改)**:
79
174
  - 策略来自 `meeting.eb.baseSet.get` → `room_conflict_chk` / `room_conflict`、`member_conflict_chk` / `member_conflict`(`chk=0` 表示关闭检测,**页面同样不校验**);
80
- - 会议室:查占用视图 `query_roomusage_list`(`address` + 时间重叠:占用开始 < 我的结束 且 占用结束 > 我的开始;占用视图只对有效状态生成,无需再过滤状态);
175
+ - 会议室:查占用视图 `query_roomusage_list`(**过滤参数 `address` = 会议室 id**;时间重叠:占用开始 < 我的结束 且 占用结束 > 我的开始;占用视图只对有效状态生成,无需再过滤状态)。⚠️ 该接口**返回体的 `address` 字段为空**,会议室要读 **`room_id`** —— 别据此误判成"检测失效";
81
176
  - 参会人:**2 次请求方案(无 N+1)** —— `query_mtDetail_list`(时间范围 + `mt_status=1,2,8,9`)取候选会议 → `query_meeting_receipt_list?mt_id=<多值批量>`(分批 + 分页拉全)→ 本地求交集;**禁止**退回「逐场会议查回执」的旧做法;
82
177
  - 参会人以**回执表 `member_id`** 为准(会议表 `hrm_members` 是渲染文本,不可靠),且只统计 `member_type=1`(参会人员);
178
+ - ⚠️ **传参口径(2026-09-20 实测)**:`hrm_members` 必须是 **HRM 员工 id**;传 E10 userId **不报错**,但与回执 `member_id` 比对不上 → **静默漏检**(表现为"无冲突")。人员 → 员工 id 用 `weaver-e10-hrm`;
83
179
  - `org_members`(参会部门)CLI **不展开**成员,因此只有 `hrm_members` 参与人员冲突检测 —— 该情况会以 warning 如实说明,需要覆盖部门成员时请先把部门展开为员工 id。
84
180
  - **冲突处置的唯一依据就是应用设置**(`room_conflict` / `member_conflict`),CLI 不额外加码、也不引入任何隐藏校验:
85
181
  - `1`「禁止提交」→ 怎么都不允许提交,直接拒绝(`conflict_rejected`);
86
- - `0`「仅提醒」→ 如实告知冲突并建议换时间/换地点;**用户坚持要新建,就可以继续新建**(`prepare` 只给 `warnings`,不阻止 `apply`)。
182
+ - `0`「仅提醒」→ 如实告知冲突并建议换时间/换地点,**是否继续由用户决定**(`prepare` 只给 `warnings`,不阻止 `apply`);CLI **不主动建议忽略冲突**。
87
183
 
88
184
  ## 建会特别说明(EB 版)
89
185
 
@@ -92,7 +188,76 @@ weaver-work-cli --json meeting run meeting.eb.meeting.create.prepare --input-jso
92
188
  - 未开启 → 可 `meeting.eb.meeting.create.prepare`。
93
189
  2. `mt_hours` 由 CLI 按 `begin_datetime`/`end_datetime` **自动计算**(小时,支持小数),无需用户提供。
94
190
  3. `caller`/`contacter` 未指明时 CLI 默认填当前用户。
95
- 4. 建会后若需占用/回执/签到数据:审批关闭(手动建会)时才由 CLI 生成 —— `meeting.eb.roomUsage.create`(占用的 `room_id` 取会议室 id、`startdate`/`enddate` 与会议一致)、`meeting.eb.receipt.create`(`member_manager` 取 `member_id`)、`meeting.eb.sign.create`(必填 `mt_id`)。
191
+ 4. ⭐ **建会成功后 CLI 会自动补建关联数据**(占用 / 回执 / 签到)—— 自定义接口 `create_meeting` **不会**像页面建会那样生成它们,而这三样是**冲突检测与补签的硬依赖**:
192
+ - **占用** `create_roomusage`:`room_id` = 会议的 `address`、`startdate`/`enddate` 与会议一致(缺它 ⇒ 会议室冲突检测查不到本场会);
193
+ - **回执** `create_meeting_receipt`:按 `hrm_members` **逐人一条**(`member_type=1`、`member_manager` = `member_id`)(缺它 ⇒ 参会人冲突检测失效);
194
+ - **签到** `create_meeting_sign`:按 `hrm_members` **逐人一条**(缺它 ⇒ `meeting.eb.sign.mark` 补签无记录可用)。
195
+ - 结果在 `apply` 返回的 **`data.association`**:`{ status: COMPLETE|PARTIAL, mtId, created: { roomUsage, receipt, sign }, warnings }`。
196
+ - **补建失败不中断建会**(主写入已成功)→ 只进 `association.warnings` 并说明后果;**不要因补建失败而重试建会**(会重复建会)。
197
+ - 未传 `address` → 跳过占用;未传 `hrm_members`(如仅用 `org_members` 指定部门,CLI 不展开部门成员)→ 跳过回执与签到,均在 warnings 中说明。
198
+ - 需要"取消会议释放占用"时用 `meeting.eb.meeting.delete`(会级联回收 占用/回执/签到);`delete_roomusage` 在本环境报「主键传入不能为空」,不可用。
199
+
200
+ ## 出席情况汇总(`data.progress`,2026-09-22)
201
+
202
+ `meeting.eb.sign.list` 与 `meeting.eb.receipt.list` 的返回也附带 `data.progress`(机制同 `decisionTask.list`:**纯 CLI 侧计算、原始 `rows` 原样返回、不动服务端**):
203
+
204
+ - **`progress.page`** = `{ pageNo, pageSize, pageCount, total }`。
205
+ ⚠️ **`summary` 里的计数只覆盖当前页**(`total` 才是全部条数)—— 要全量统计请传足够大的 `pageSize`(上限 200)或逐页累加,别把当页数字当总数。
206
+ - **签到** `progress.summary`:`total` / `signed` / `notSigned` / `signedOut` / `attendanceRate` / `byState`(`normal`·`leave`·`late`·`earlyLeave`·`notSignedOut`·`lateAndNotSignedOut`·`lateAndEarlyLeave`·`absent`·`notSigned`·`unknown`)。
207
+ ⚠️ **未签到 = `is_sign` 与 `sign_time` 都为空**;**`is_sign=0` 是「缺席」**(已表态没来)—— 两者语义不同,别混在一起算出席率。
208
+ - **回执** `progress.summary`:`total` / `attend` / `decline` / `noReply` / `replyRate` / `attendRate` / `byMemberType`(`attendee`·`customer`·`other`)/ `bookRoom` / `bookTicket`。
209
+ ⚠️ **`attend_status` 为空 = 未回执**(不是"不参加");`0` 不参加、`1` 参加。
210
+ - 两张表的 `mt_id` 语义不同:**签到表回的是会议名称文本,回执表回的是会议 id** —— 汇总里统一叫 `mtRef`(原样透出)。
211
+
212
+ ## 详情页链接(`meeting.link`,2026-09-23 新增)
213
+
214
+ 链接地址**随模式不同**,且 EB 版必须按表 tag 解析 `objid`(随环境变化、**不等于 formId**),
215
+ 因此**禁止手工拼 URL** —— 一律用 `meeting.link` 生成(`objid` 按 `baseUrl|tenantKey` 长期缓存)。
216
+
217
+ | 模式 | 对象 | URL 模板 |
218
+ |---|---|---|
219
+ | EB 版 | 全部对象 | `{baseUrl}/sp/ebdfpage/card/0/{objid}/{dataid}`(`card` 后的 `0` **恒为 0**) |
220
+ | 标准版 | 会议详情 | `{baseUrl}/sp/meeting/meetingSingle?meetingId={meetingid}` |
221
+ | 标准版 | 会议室 / 会议类型 | 没有单页 → 只输出数据、不附链接 |
222
+
223
+ EB 表 tag 映射(`meeting.link` 的 `type` 取值,也可用 `tag` 直接指定表 tag):
224
+
225
+ | type | 表 tag | 对象 |
226
+ |---|---|---|
227
+ | `meeting`(默认) | `uf_mt_detail` | 会议 |
228
+ | `room` | `uf_mt_room_base` | 会议室 |
229
+ | `roomType` | `uf_mt_room_type` | 会议室类型 |
230
+ | `roomUsage` | `uf_mt_room_usage` | 会议室占用 |
231
+ | `setting` | `uf_mt_base_set` | 应用设置 |
232
+ | `mtType` | `uf_mt_type` | 会议类型 |
233
+ | `sign` | `uf_mt_sign` | 签到 |
234
+ | `receipt` | `uf_mt_receipt_member` | 回执 |
235
+ | `topic` | `uf_mt_topic` | 议题 |
236
+ | `minutes` | `uf_mt_minutes` | 纪要 |
237
+
238
+ Windows PowerShell:
239
+
240
+ ```powershell
241
+ weaver-work-cli --json meeting run meeting.link --input-json '{"dataId":"1305624900858404950","type":"meeting","name":"周会"}'
242
+ weaver-work-cli --json meeting run meeting.link --input-json '{"dataIds":["1305624900858404950","1305624900858404951"],"name":"周会"}'
243
+ ```
244
+
245
+ macOS/Linux(bash/zsh):
246
+
247
+ ```bash
248
+ weaver-work-cli --json meeting run meeting.link --input-json '{"dataId":"1305624900858404950","type":"meeting","name":"周会"}'
249
+ weaver-work-cli --json meeting run meeting.link --input-json '{"dataIds":["1305624900858404950","1305624900858404951"],"name":"周会"}'
250
+ ```
251
+
252
+ **输出与用法**:
253
+
254
+ - 单条:`data.path` / `data.url` / `data.markdown`(只有传了 `name` 才带 markdown)。
255
+ - 批量:`data.items[]`(列表输出时一次生成全部链接,别逐条调用)。
256
+ - `data.source`:`dynamic`(本次解析到 objid)/ `cache`(命中缓存)/ `none`(未取到或标准版无单页)。
257
+ - **解析失败只降级**:`url=null` + `warnings`,不抛错、不影响主业务结果 —— 如实说明"链接暂不可用",
258
+ **不要**因此重试,更不要自己拼 URL。
259
+ - `mode` 默认 `auto`(复用 `meeting.env.detect` 的模式判定与缓存);只有明确知道模式时才显式传 `eb` / `standard`。
260
+ - **输出格式**:链接必须内联到名称单元格(`[会议名称](url)`),**禁止**单列「详情链接」一行。
96
261
 
97
262
  ## 注意
98
263
 
@@ -110,5 +275,5 @@ weaver-work-cli --json meeting run meeting.eb.meeting.create.prepare --input-jso
110
275
  - `eb_write_not_applied`:接口返回 `datajson.status=false`,服务端未生效,核对字段与权限。
111
276
  - `target_changed` / `continuation_expired`:重新 prepare 并再次确认。
112
277
  - `partial/write_uncertain`:写请求发出后连接中断,**禁止自动重试**,先 list/detail 回查是否已生效。
113
- - `conflict_rejected`:命中冲突且应用设置为**禁止提交** —— 立即停止,建议用户换时间或换地点;若应用设置为**仅提醒**,则只写入 `warnings`(不抛错),**用户了解冲突后坚持新建时仍可继续提交**。
278
+ - `conflict_rejected`:命中冲突且应用设置为**禁止提交** —— 立即停止,建议用户换时间或换地点;若应用设置为**仅提醒**,则只写入 `warnings`(不抛错),**是否继续由用户决定**(CLI 不主动建议忽略冲突)。
114
279
  - `conflict_check_failed`:冲突检测接口调用失败(fail-closed,直接拒绝建会),核对占用/回执接口权限后重试。
@@ -9,8 +9,13 @@
9
9
  ### 回执
10
10
 
11
11
  - 回执更新**必须传回执记录主键 `id`**(不是 `memberId`);`prepare` 会自动按 `memberId` 回查定位。
12
+ - ⚠️ **`memberId` 字段语义(2026-10-08 真机实测,两个环境一致)**:`meeting.receipt.list` 返回的一条回执里,
13
+ **`memberId` 是「姓名」、`memberIdId` 才是「人员 id」**。因此 `receipt.update.prepare` 的 `memberId`
14
+ **两种写法都接受**(人员 id 或姓名);CLI 会按 `memberIdId` / `memberId` 依次匹配,并在写请求 body 里统一带上**解析后的人员 id**。
15
+ 列表输出的原始字段保持原样(`memberId`=姓名、`memberIdId`=人员 id),**不要**把 `memberId` 当成 id 使用。
12
16
  - `attendStatus` 枚举:`0`=未填写、`1`=参加、`2`=不参加(回执"否/请假"传 2)。
13
- - 回执列表接口必须带 `isMobile`(CLI 缺省 `false`;缺失会导致后端 NPE→500,已被 CLI 托管)。
17
+ - 回执列表接口必须带 `isMobile`(CLI 缺省 `false`)**与 `memberType`(CLI 缺省 `1`)**:2026-10-08 真机实测
18
+ **不传 `memberType` 必报 500「系统内部错误」**(此前只挡住了 `isMobile`),两者均已由 CLI 托管,调用方无需显式传。
14
19
 
15
20
  ### 签到
16
21
 
@@ -30,7 +35,7 @@ Windows PowerShell:
30
35
  weaver-work-cli --json meeting run meeting.receipt.list --input-json '{"mtId":"1305624900858404950"}'
31
36
  weaver-work-cli --json meeting run meeting.receipt.update.prepare --input-json '{"mtId":"1305624900858404950","memberId":"101","attendStatus":2,"remark":"请假"}'
32
37
  # 用户确认后:
33
- weaver-work-cli --json meeting run meeting.receipt.update.apply --input-json '{"confirm":true,"continuation":"<prepare返回的continuation>","mtId":"1305624900858404950","memberId":"101","attendStatus":2,"remark":"请假"}'
38
+ weaver-work-cli --json meeting run meeting.receipt.update.apply --input-json '{"confirm":true,"continuation":"<prepare返回的continuation>"}'
34
39
  weaver-work-cli --json meeting run meeting.sign.list --input-json '{"mtId":"1305624900858404950"}'
35
40
  # 补签(id 取 sign.list 里的 signs[].id):
36
41
  weaver-work-cli --json meeting run meeting.sign.mark.prepare --input-json '{"id":"1305624900858404951"}'