weaver-work-cli 0.1.22 → 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.
@@ -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_DATETIME_MINUTE, SCHEMA_ID } from './types.js';
4
- import { assertDateTimeMinute, extractRows, toIdCsv, toIdString } from './shared.js';
4
+ import { assertDateTimeMinute, extractRows, mergeSnapshot, toIdCsv, toIdString } from './shared.js';
5
5
  const CONTINUATION_TTL_SECONDS = 600;
6
6
  const LIST_PATH = '/api/meeting/search/getMeetingList';
7
7
  function fingerprint(value) {
@@ -202,15 +202,18 @@ const meetingChangeApply = {
202
202
  caller: SCHEMA_ID,
203
203
  contacter: SCHEMA_ID,
204
204
  },
205
- required: ['confirm', 'continuation', 'id'],
205
+ required: ['confirm', 'continuation'],
206
206
  },
207
207
  handler: async (args, env) => {
208
208
  requireConfirm(args, 'meeting.change.apply');
209
209
  const lease = await verifyLease(args, 'meeting.change.apply', env);
210
- const body = normalizeChangeInput(args);
210
+ // 只传 confirm+continuation 时用 prepare 快照(含 prepare 时算好的 id / 字段);
211
+ // 传入字段则覆盖快照,仍由 fingerprint 决定是否 target_changed。
212
+ const merged = mergeSnapshot(lease.payload, args);
213
+ const { operateType: leaseOperateType, ...mergedBody } = merged;
214
+ const body = normalizeChangeInput(mergedBody);
211
215
  const id = String(body.id);
212
216
  const { operateType, mtStatus } = await resolveOperateType(id, env, 'meeting.change.apply');
213
- const leaseOperateType = lease.payload?.operateType;
214
217
  if (leaseOperateType && leaseOperateType !== operateType) {
215
218
  throw meetingValidationError('target_changed', `会议状态已变化(prepare 时按 ${leaseOperateType}、当前按 ${operateType},当前状态:${mtStatus}),请重新执行 prepare 并向用户确认`);
216
219
  }
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { meetingConflictError, meetingConfirmationError, meetingPartialError, meetingValidationError } from '../errors.js';
3
3
  import { meetingSuccess, SCHEMA_DATETIME_MINUTE, SCHEMA_ID } from './types.js';
4
- import { assertDateTimeMinute, extractConflict, toIdCsv, toIdString, } from './shared.js';
4
+ import { assertDateTimeMinute, extractConflict, mergeSnapshot, toIdCsv, 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);
@@ -309,13 +309,15 @@ const meetingCreateApply = {
309
309
  repeatDesc: { type: 'string' },
310
310
  serviceItems: { type: 'string' },
311
311
  },
312
- required: ['confirm', 'continuation', 'name', 'beginDatetime', 'endDatetime'],
312
+ required: ['confirm', 'continuation'],
313
313
  },
314
314
  handler: async (args, env) => {
315
315
  requireConfirm(args, 'meeting.create.apply');
316
316
  const lease = await verifyLease(args, 'meeting.create.apply', env);
317
317
  const session = await env.host.getSession();
318
- const body = normalizeCreateInput(args, session.userId);
318
+ // 只传 confirm+continuation 时用 prepare 快照(用户已确认的内容);传入其它字段则作为覆盖,
319
+ // 由下面的 fingerprint 校验决定是否 target_changed。
320
+ const body = normalizeCreateInput(mergeSnapshot(lease.payload, args), session.userId);
319
321
  if (lease.fingerprint !== fingerprint(body)) {
320
322
  throw meetingValidationError('target_changed', '会议内容与 prepare 时的快照不一致,请重新执行 prepare');
321
323
  }
@@ -21,6 +21,13 @@ import { meetingPartialError, meetingValidationError } from '../../errors.js';
21
21
  import { toIdString } from '../shared.js';
22
22
  const CONTINUATION_TTL_SECONDS = 600;
23
23
  const DATE_PATTERN = '^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$';
24
+ /** 本地时间的 yyyy-MM-dd HH:mm:ss(EB 的签到/完成时间字段要求带秒)。 */
25
+ function formatSecondNow() {
26
+ const now = new Date();
27
+ const pad = (value) => String(value).padStart(2, '0');
28
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} `
29
+ + `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
30
+ }
24
31
  /** 决议状态:0未决议 / 1决议临时保存 / 2决议提交完成。 */
25
32
  const DECISION_DONE = '2';
26
33
  /** 会议状态「结束」:产生决议后会议即视为结束。 */
@@ -293,7 +300,19 @@ const signMarkPrepare = {
293
300
  throw meetingValidationError('sign_record_not_found', `未在该会议(${mtId})中找到员工 ${userId} 的签到记录,无法补签。`
294
301
  + '请先用 meeting.eb.sign.list 确认该会议是否已生成签到数据(若会议尚无签到记录,属建会初始化问题,需先用 meeting.eb.sign.create 初始化后再补签)。', { mtId, userId });
295
302
  }
296
- const update = normalizeEbWrite(requireEndpoint('update_meeting_sign_info'), { ...args, id: recordId, mt_id: mtId }, session.userId);
303
+ // ⚠️ 2026-10-08 真机实测:只传 mt_id + user_id 时,update 里**没有任何时间字段**,
304
+ // 服务端照常返回成功、CLI 报 COMPLETE,但签到记录**一点没变**(静默无效)。
305
+ // 补签的语义就是"把签到时间补录为当前时间",故缺省补当前时间,并明确告警;
306
+ // 补签退同理(只传 user_id 而不给 signback_time 时补当前时间)。
307
+ const warnings = [];
308
+ const patch = { ...args, id: recordId, mt_id: mtId };
309
+ if (patch.sign_time === undefined && patch.signback_time === undefined && patch.sign_out_status === undefined) {
310
+ const now = formatSecondNow();
311
+ patch.sign_time = now;
312
+ patch.is_sign = patch.is_sign ?? '1';
313
+ warnings.push(`未传签到时间:按"补签"语义使用当前时间 ${now}(如需指定时间请传 sign_time)`);
314
+ }
315
+ const update = normalizeEbWrite(requireEndpoint('update_meeting_sign_info'), patch, session.userId);
297
316
  const payload = { mtId, signRecordId: recordId, update };
298
317
  const token = await env.continuation.issue({
299
318
  operation: 'meeting.eb.sign.mark.apply',
@@ -316,10 +335,11 @@ const signMarkPrepare = {
316
335
  continuation: token,
317
336
  expiresInSeconds: CONTINUATION_TTL_SECONDS,
318
337
  }, {
338
+ warnings,
319
339
  meta: {
320
340
  notes: [
321
341
  '走的是**更新**(update_meeting_sign_info),不会新增签到记录。',
322
- '未传 sign_time 时签到时间仍为空,请按需补上。',
342
+ '未传 sign_time / signback_time 时视为「补签」,由 CLI 用当前时间补上(preview.after 里可见实际写入值)。',
323
343
  ],
324
344
  },
325
345
  });
@@ -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 { assertMeetingExists, toIdString } from './shared.js';
4
+ import { assertMeetingExists, mergeSnapshot, 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);
@@ -84,14 +84,16 @@ function lifecyclePair(spec) {
84
84
  properties: {
85
85
  confirm: { const: true },
86
86
  continuation: { type: 'string', minLength: 1 },
87
- id: { ...SCHEMA_ID, description: '会议 id(必须与 prepare 时一致)' },
87
+ id: { ...SCHEMA_ID, description: '会议 id(可选:省略时取 prepare 快照中的 id)' },
88
88
  },
89
- required: ['confirm', 'continuation', 'id'],
89
+ required: ['confirm', 'continuation'],
90
90
  },
91
91
  handler: async (args, env) => {
92
92
  requireConfirm(args, applyName);
93
93
  const lease = await verifyLease(args, applyName, env);
94
- const id = toIdString(args.id, 'id');
94
+ // 只传 confirm+continuation 时用 prepare 快照里的 id(与 create/change 的 apply 行为一致)。
95
+ const merged = mergeSnapshot(lease.payload, args);
96
+ const id = toIdString(merged.id, 'id');
95
97
  if (lease.fingerprint !== fingerprint({ id })) {
96
98
  throw meetingValidationError('target_changed', '会议 id 与 prepare 时的快照不一致,请重新执行 prepare');
97
99
  }
@@ -129,11 +131,11 @@ export const meetingLifecycleOperations = [
129
131
  ...lifecyclePair({
130
132
  action: 'delete',
131
133
  resource: 'meeting',
132
- summaryPrepare: '准备删除会议记录:回查会议存在性并生成确认预览,不写入(彻底删除)',
134
+ summaryPrepare: '准备删除会议记录:回查会议存在性并生成确认预览,不写入(软删:进回收站,可还原)',
133
135
  summaryApply: '提交删除会议记录(deleteMeetingDetail,源码为表单绑定,参数走 query);必须在用户明确确认后调用',
134
136
  path: '/api/meeting/detail/deleteMeetingDetail',
135
137
  via: 'query',
136
138
  bodyOf: (id) => ({ id }),
137
- note: '删除是彻底移除会议记录(区别于取消);不可逆,需用户明确确认。服务端会按会议状态判定删除权限,无权限时返回“您没有删除权限!”。',
139
+ note: '删除是**软删**(服务端 update mt_detail set delete_type=2):会议移入回收站、可从回收站还原,正常列表中不再出现;需要还原请引导用户到会议回收站页面操作(CLI 暂无还原能力)。服务端会按会议状态判定删除权限,无权限时返回“您没有删除权限!”。',
138
140
  }),
139
141
  ];
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { 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);
@@ -30,7 +30,7 @@ const meetingReceiptList = {
30
30
  resource: 'receipt',
31
31
  method: 'receipt.list',
32
32
  action: 'meeting.receipt.list',
33
- summary: '查询会议回执列表(getMeetingReceiptMemberInfoList;isMobile 缺省 false,缺失会导致后端 NPE)',
33
+ summary: '查询会议回执列表(getMeetingReceiptMemberInfoList;isMobile 缺省 false、memberType 缺省 1,二者缺失均会导致后端 NPE→500)',
34
34
  capability: 'receipt',
35
35
  risk: 'read',
36
36
  inputSchema: {
@@ -38,8 +38,8 @@ const meetingReceiptList = {
38
38
  additionalProperties: false,
39
39
  properties: {
40
40
  mtId: { ...SCHEMA_ID, description: '会议 id(必填)' },
41
- memberType: { type: 'integer', enum: [1, 2, 3], description: '人员类型:1=人员 2=客户 3=其他(默认 1)' },
42
- isMobile: { type: 'boolean', description: '移动端标记(默认 false)' },
41
+ memberType: { type: 'integer', enum: [1, 2, 3], description: '人员类型:1=人员 2=客户 3=其他(默认 1,**不传会导致后端 500**)' },
42
+ isMobile: { type: 'boolean', description: '移动端标记(默认 false,缺失会导致后端 NPE→500)' },
43
43
  current: { type: 'integer' },
44
44
  pageSize: { type: 'integer' },
45
45
  },
@@ -49,11 +49,12 @@ const meetingReceiptList = {
49
49
  const body = {
50
50
  mtId: toIdString(args.mtId, 'mtId'),
51
51
  isMobile: args.isMobile === undefined ? false : args.isMobile === true,
52
+ // 2026-10-08 真机实测(10.12.105.186):**不传 memberType 必 500「系统内部错误」**,
53
+ // 传 1 即正常返回。历史文档只提醒了 isMobile,实际 memberType 同样不能缺省。
54
+ memberType: args.memberType === undefined ? 1 : args.memberType,
52
55
  current: typeof args.current === 'number' && args.current > 0 ? args.current : 1,
53
56
  pageSize: typeof args.pageSize === 'number' && args.pageSize > 0 ? args.pageSize : 50,
54
57
  };
55
- if (args.memberType !== undefined)
56
- body.memberType = args.memberType;
57
58
  const response = await host.request({
58
59
  method: 'POST',
59
60
  path: '/api/meeting/receiptMember/getMeetingReceiptMemberInfoList',
@@ -69,7 +70,9 @@ const meetingReceiptList = {
69
70
  // ---------------------------------------------------------------------------
70
71
  function normalizeReceiptUpdate(args) {
71
72
  const mtId = toIdString(args.mtId, 'mtId');
72
- const memberId = toIdString(args.memberId, 'memberId');
73
+ // memberId 允许两种形态:人员 id(数字)或姓名文本 —— 列表返回的 `memberId` 字段其实是**姓名**
74
+ // (人员 id 在 `memberIdId`),若只接受数字会与列表结果自相矛盾(2026-10-08 真机实测)。
75
+ const memberId = toMemberSelector(args.memberId);
73
76
  const attendStatus = args.attendStatus;
74
77
  if (attendStatus !== 0 && attendStatus !== 1 && attendStatus !== 2) {
75
78
  throw meetingValidationError('attendStatus_invalid', 'attendStatus 仅允许 0=未填写 1=参加 2=不参加(回执"否"传 2)');
@@ -81,8 +84,24 @@ function normalizeReceiptUpdate(args) {
81
84
  ...(args.remark !== undefined ? { remark: String(args.remark) } : {}),
82
85
  };
83
86
  }
84
- /** 回查回执列表,按 memberId 定位回执记录 id(建会时后端自动生成,每参会人一条)。 */
85
- async function locateReceiptId(mtId, memberId, env) {
87
+ /**
88
+ * 人员选择器:数字 → 人员 id;其它非空文本 → 姓名(用于按名字定位回执记录)。
89
+ * 列表返回里 `memberId` 是姓名、`memberIdId` 才是人员 id,故两种写法都要接受。
90
+ */
91
+ function toMemberSelector(value) {
92
+ if (typeof value === 'number')
93
+ return toIdString(value, 'memberId');
94
+ if (typeof value === 'string') {
95
+ const trimmed = value.trim();
96
+ if (/^\d+$/u.test(trimmed))
97
+ return trimmed;
98
+ if (trimmed)
99
+ return trimmed;
100
+ }
101
+ throw meetingValidationError('memberId_invalid', 'memberId 必须是人员 id(数字)或参会人姓名');
102
+ }
103
+ /** 回查回执列表,按人员 id / 姓名定位回执记录 id(建会时后端自动生成,每参会人一条)。 */
104
+ async function locateReceipt(mtId, memberId, env) {
86
105
  const response = await env.host.request({
87
106
  method: 'POST',
88
107
  path: '/api/meeting/receiptMember/getMeetingReceiptMemberInfoList',
@@ -90,11 +109,20 @@ async function locateReceiptId(mtId, memberId, env) {
90
109
  body: { mtId, memberType: 1, isMobile: false, current: 1, pageSize: 100 },
91
110
  });
92
111
  const { rows } = extractRows(response);
93
- const hit = rows.find((row) => String(row?.memberId) === memberId);
112
+ // ⚠️ 列表行的字段语义(2026-10-08 真机实测):`memberId` = 姓名、`memberIdId` = 人员 id。
113
+ // 老实现只比对 `memberId`,传入真实人员 id 永远匹配不到 ⇒ 回执更新不可用。
114
+ const hit = rows.find((row) => {
115
+ const byId = String(row?.memberIdId ?? '') === memberId;
116
+ const byName = String(row?.memberId ?? '').trim() === memberId;
117
+ return byId || byName;
118
+ });
94
119
  if (!hit || hit.id === undefined) {
95
- throw meetingValidationError('receipt_not_found', `未找到该参会人(memberId=${memberId})的回执记录,请先通过 meeting.receipt.list 核对`);
120
+ throw meetingValidationError('receipt_not_found', `未找到该参会人(memberId=${memberId})的回执记录,请先通过 meeting.receipt.list 核对(列表中 memberId 是姓名、memberIdId 是人员 id)`);
96
121
  }
97
- return String(hit.id);
122
+ return {
123
+ receiptId: String(hit.id),
124
+ memberIdResolved: String(hit.memberIdId ?? hit.memberId ?? memberId),
125
+ };
98
126
  }
99
127
  const meetingReceiptUpdatePrepare = {
100
128
  name: 'meeting.receipt.update.prepare',
@@ -110,7 +138,7 @@ const meetingReceiptUpdatePrepare = {
110
138
  additionalProperties: false,
111
139
  properties: {
112
140
  mtId: { ...SCHEMA_ID, description: '会议 id' },
113
- memberId: { ...SCHEMA_ID, description: '参会人员 id(用于定位回执记录)' },
141
+ memberId: { type: 'string', description: '参会人员 id 或姓名(用于定位回执记录;列表里 memberId 是姓名、memberIdId 才是人员 id,两者都接受)' },
114
142
  attendStatus: { type: 'integer', enum: [0, 1, 2], description: '0=未填写 1=参加 2=不参加' },
115
143
  remark: { type: 'string', description: '备注(如"请假")' },
116
144
  },
@@ -118,9 +146,10 @@ const meetingReceiptUpdatePrepare = {
118
146
  },
119
147
  handler: async (args, env) => {
120
148
  const normalized = normalizeReceiptUpdate(args);
121
- const receiptId = await locateReceiptId(String(normalized.mtId), String(normalized.memberId), env);
149
+ const located = await locateReceipt(String(normalized.mtId), String(normalized.memberId), env);
122
150
  const session = await env.host.getSession();
123
- const payload = { id: receiptId, ...normalized };
151
+ // 写请求 body 统一带**解析后的人员 id**(列表里的 memberId 是姓名,直接回传姓名对服务端无意义)。
152
+ const payload = { id: located.receiptId, ...normalized, memberId: located.memberIdResolved };
124
153
  const token = await env.continuation.issue({
125
154
  operation: 'meeting.receipt.update.apply',
126
155
  baseUrl: session.baseUrl,
@@ -153,18 +182,22 @@ const meetingReceiptUpdateApply = {
153
182
  confirm: { const: true },
154
183
  continuation: { type: 'string', minLength: 1 },
155
184
  mtId: SCHEMA_ID,
156
- memberId: SCHEMA_ID,
185
+ memberId: { type: 'string' },
157
186
  attendStatus: { type: 'integer', enum: [0, 1, 2] },
158
187
  remark: { type: 'string' },
159
188
  },
160
- required: ['confirm', 'continuation', 'mtId', 'memberId', 'attendStatus'],
189
+ required: ['confirm', 'continuation'],
161
190
  },
162
191
  handler: async (args, env) => {
163
192
  requireConfirm(args, 'meeting.receipt.update.apply');
164
193
  const lease = await verifyLease(args, 'meeting.receipt.update.apply', env);
165
- const normalized = normalizeReceiptUpdate(args);
166
- if (lease.fingerprint !== fingerprint(normalized)) {
167
- throw meetingValidationError('target_changed', '回执内容与 prepare 时的快照不一致,请重新执行 prepare');
194
+ // 只传 confirm+continuation 时直接用 prepare 快照;传了业务字段则与快照合并后校验一致性。
195
+ const business = pickBusinessInput(args);
196
+ if (Object.keys(business).length > 0) {
197
+ const normalized = normalizeReceiptUpdate(mergeSnapshot(lease.payload, args));
198
+ if (lease.fingerprint !== fingerprint(normalized)) {
199
+ throw meetingValidationError('target_changed', '回执内容与 prepare 时的快照不一致,请重新执行 prepare');
200
+ }
168
201
  }
169
202
  const payload = lease.payload;
170
203
  let response;
@@ -178,7 +211,7 @@ const meetingReceiptUpdateApply = {
178
211
  }
179
212
  catch (error) {
180
213
  if (isNetworkError(error)) {
181
- throw meetingPartialError('回执更新请求发送后连接中断,是否已生效不确定,禁止自动重试,请通过 meeting.receipt.list 复核', { attempted: { mtId: normalized.mtId, memberId: normalized.memberId } }, { stage: 'receipt-update' });
214
+ throw meetingPartialError('回执更新请求发送后连接中断,是否已生效不确定,禁止自动重试,请通过 meeting.receipt.list 复核', { attempted: { mtId: payload.mtId, memberId: payload.memberId } }, { stage: 'receipt-update' });
182
215
  }
183
216
  throw error;
184
217
  }
@@ -61,6 +61,34 @@ export function toIdString(value, label) {
61
61
  return value;
62
62
  throw meetingValidationError('id_invalid', `${label} 必须是数字 id(超长 id 用字符串)`);
63
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
+ }
64
92
  /** 逗号分隔 id 串:接受数组或字符串,输出字符串。 */
65
93
  export function toIdCsv(value, label) {
66
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weaver-work-cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "面向泛微工作工具链的可扩展 TypeScript CLI 基础框架(E10 业票通等能力)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -5,7 +5,7 @@ display_name_en: Weaver E10 Meeting Management
5
5
  description: "泛微 E10 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]-会议议题(查看/更新/删除)、议题类型、会议纪要查询、会议类型与应用设置、会议决议发布与决议任务跟踪。认证与会话由 weaver-e10-shared 提供。"
6
6
  description_zh: "泛微 E10 会议管理,帮您处理会议日常事务:[会议]-查询今天/本周/我的会议与会议详情,预约建会(自动做会议室与参会人冲突检测),会议改期/换地点/改参会人、取消、删除;[会议室]-查询会议室与占用/空闲情况;[参会与回执]-查参会人名单、填写/更新会议回执;[签到]-查签到与签退情况、补签、补签退、清除签到记录;[会议资料]-会议议题(查看/更新/删除)、议题类型、会议纪要查询、会议类型与应用设置、会议决议发布与决议任务跟踪。认证与会话由 weaver-e10-shared 提供。"
7
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
8
+ version: 2.13.0
9
9
  author: 泛微网络科技股份有限公司
10
10
  agent_created: true
11
11
  requires:
@@ -134,6 +134,7 @@ cliHelp: "weaver-work-cli meeting --help"
134
134
  3. `meeting.create.prepare`:自动执行建会路径检查(`onlyFlowCreate=1` 或周期工作流 → 拒绝并提示走流程)与**强制冲突检测**(接口失败视为冲突;`cansub=false` 直接拒绝;`cansub=true` 有冲突描述 → warnings 如实告知并建议换时间/地点,**但不阻止提交**,是否继续由用户决定;CLI 不主动建议忽略冲突)。
135
135
  4. 向用户摘要 preview(含 caller/contacter 默认当前用户、周期 `repeatDesc` 自动拼接)。
136
136
  5. 用户明确确认 → `meeting.create.apply`(`confirm: true` + continuation,**10 分钟内有效,过期重新 prepare**)。
137
+ **apply 的入参口径(2026-10-08 修正)**:只传 `confirm` + `continuation` 即可 —— 业务字段以 prepare 快照为准(快照就是用户刚确认过的内容);若同时传了业务字段,则必须与快照一致,否则报 `target_changed`(提示重新 prepare),**不会**绕过确认闸门。
137
138
  6. 结果回读用 `meeting.list`;会议链接一律用 `meeting.link` 生成(**禁止手工拼 URL**:EB 版 objid 需动态解析,标准版会议室/会议类型没有单页),再按 `[名称](url)` **内联到名称单元格**输出。
138
139
 
139
140
  ## 处理链:变更 / 取消 / 删除会议(高风险写,强制确认链)
@@ -141,7 +142,7 @@ cliHelp: "weaver-work-cli meeting --help"
141
142
  1. 先用 `meeting.list`(或用户给出的会议 id)定位目标会议,必要时用 `meeting.link` 生成 `[名称](url)` 给用户核对(EB 环境会议也在 `meeting.eb.meeting.list`)。
142
143
  2. `meeting.change.prepare`(变更)/ `meeting.cancel.prepare` / `meeting.delete.prepare`(入参 `id`):CLI 先**回查会议存在性**;`change` 还会按会议状态自动决定 `operateType`(草稿=UPDATE、正常=CHANGE)。**权限判定交给服务端**,CLI 不做权限预判。成功返回 `preview` + `continuation`(10 分钟有效)。
143
144
  3. 向用户摘要动作与后果(变更=覆盖所提交字段、当前状态与分流类型;取消=会议进入取消状态、占用/回执/签到失效;删除=**移入回收站**、不再出现在正常列表),**取得明确确认**。变更只提交 preview 中列出的字段。
144
- 4. 用户确认 → `meeting.*.apply`(`confirm: true` + continuation)。
145
+ 4. 用户确认 → `meeting.*.apply`(`confirm: true` + continuation;**只传这两个参数即可**,业务字段取 prepare 快照;改动内容会报 `target_changed`)。
145
146
  5. 结果回读:`meeting.list` 确认状态(取消/删除后该会议不应再出现在正常列表)。
146
147
  6. **禁止**跳过 prepare 直接 apply;**禁止**在未确认目标存在时执行写操作。
147
148
 
@@ -43,6 +43,11 @@
43
43
  | **决议任务** | `meeting.eb.decisionTask.list` | `meeting.eb.decisionTask.update` | query_mt_decision_task_list(+count) / update_mt_decision_task |
44
44
  | **补签(语义化)** | — | `meeting.eb.sign.mark` | update_meeting_sign_info(自动定位签到记录 id)|
45
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`,带秒)。
50
+
46
51
  ## 常用命令
47
52
 
48
53
  Windows PowerShell:
@@ -137,7 +142,12 @@ weaver-work-cli --json meeting run meeting.eb.meeting.create.prepare --input-jso
137
142
  - 决议可能已写入但**没返回 `dataIds`** → 抛 `partial`,提示先用 `decision.list(mt_id)` 回查,**同样不要重复创建**。
138
143
  6. **任务与进度的读法(真机实测口径)**:
139
144
  - **一条决议明细 = 一个任务**;明细里写多个执行人 ⇒ 该任务**多人共同跟进**(实测:1 明细 + 2 执行人 → 只生成 1 个任务)。
145
+ - ⚠️ **2026-10-08 复测出现「1 明细 + 1 执行人 → 生成 2 条任务」**:任务条数由**用户配置的 ESB 联动**决定,
146
+ 可能随模板/配置变化,**不要用「明细条数 = 任务数」推算**,一律以 `decisionTask.list` 的实际返回为准。
140
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=结束`)。
141
151
  **`finish_date` 是服务端必填**;把任务改回「未完成」时传一个**空格**即可显式清空。
142
152
  **同一执行人重复更新 = 覆盖该人记录,不同执行人 = 追加**(实测两人跟进后 detail1 保留两条,互不覆盖)。
143
153
  - ★ **读进度直接用 `decisionTask.list` 返回的 `data.progress`**(不必自己解析 detail1):
@@ -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"}'
@@ -34,7 +34,7 @@
34
34
  | `R-DETAIL` | `ok=true` 且 `data` 非空;id 不存在时必须报 `not_found`(**不是**返回空对象) |
35
35
  | `R-COUNT` | `withCount:true` 时 `total` 存在;count 与 list 允许不一致时以 `max(total, count)` 为准 |
36
36
  | `W-PREP` | `status=AWAITING_CONFIRMATION`,含 `preview`(path/body)+ `continuation`;EB 的 update/delete `preview.path` **必带 `?id=<主键>`** |
37
- | `W-APPLY` | `status=COMPLETE`;随后按"回读"列核对 |
37
+ | `W-APPLY` | `status=COMPLETE`;随后按"回读"列核对。**入参口径:只传 `confirm`+`continuation` 即可**(业务字段取 prepare 快照);若同时传业务字段,必须与快照一致,否则 `target_changed` |
38
38
  | `W-FAIL-SAFE` | 缺必填 → `missing_required_field`;未确认 → `confirmation.required`;主键缺失 → 服务端原文报错(不得静默成功) |
39
39
  | `W-CLEAN` | 用删除类 operation 清理自建数据;EB 清理首选 `meeting.eb.meeting.delete`(级联回收占用/回执/签到) |
40
40
 
Binary file
@@ -5,11 +5,13 @@ display_name: 泛微E10事井然项目管理
5
5
  display_name_en: Weaver E10 Shijingran Project Management
6
6
  description_zh: 泛微E10事井然项目管理助手,封装项目和任务的创建、查询、修改、删除操作,并提供项目/任务类型、紧急程度、状态等字典查询能力。适用于在对话中通过 Agent 创建、查询、修改、删除项目或任务等场景。
7
7
  description_en: Weaver E10 Shijingran project management assistant. Covers CRUD operations for projects and tasks, and provides dictionary queries for project/task types, priorities, and statuses. Used when an Agent needs to create, query, modify, or delete projects or tasks in conversation.
8
- version: 1.0.7
8
+ version: 1.0.8
9
9
  author: 泛微网络科技股份有限公司
10
10
  agent_created: true
11
11
  requires:
12
12
  bins: ["weaver-work-cli"]
13
+ dependencies:
14
+ - weaver-e10-shared
13
15
  cliHelp: "weaver-work-cli project --help"
14
16
  ---
15
17
 
@@ -26,6 +28,28 @@ cliHelp: "weaver-work-cli project --help"
26
28
  先读取 [`../weaver-e10-shared/SKILL.md`](../weaver-e10-shared/SKILL.md)。若共享规则不存在、CLI 未安装或读取失败,说明本技能的必需依赖缺失,必须调用宿主提供的提问/确认弹框,向用户说明必须补齐 `weaver-work-cli` 与共享 skill `weaver-e10-shared`(不补齐本技能无法执行)及补齐计划,并把下面两条待执行命令原样完整展示在确认信息中;Agent 不能在未确认时自行安装。用户明确确认后,Agent 必须直接代为按顺序执行且每条仅执行一次,不得要求或引导用户手动执行:① 缺 CLI 时执行 `npm install -g weaver-work-cli`;② 再执行 `weaver-work-cli skills install shared --target-dir <SKILLS_ROOT>` 装回共享 skill,`<SKILLS_ROOT>` 取本 SKILL.md 所在目录的上一级绝对目录(即与本技能同级的 skills 根目录),不要省略 `--target-dir`——省略会装进默认目录,导致本技能依然读不到共享规则。命令均成功后重新读取共享规则;仍读不到就停止并把结果报告用户,不得凭猜测继续。
27
29
  **`--is-connector` 判据**:用户明确使用连接器、或上下文要求用连接器完成任务 → 传 `true`;本次任务或上下文直接选择单独技能 → 不传;用户没有明确说明时,按**正在运行的这份 Skill 是哪一级**定——**用户级(条目标 `(user)`)不传,连接器级(标 `(connector)`)传**(同名 Skill 只会运行用户级那一份)。传 `true` 时不要再写 `--profile` / `--base-url`。完整规则见 [`../weaver-e10-shared/SKILL.md`](../weaver-e10-shared/SKILL.md)。
28
30
 
31
+ ### 登录与请求头凭证契约
32
+
33
+ 本 Skill 是 CLI 型技能,不自行发起 HTTP 请求:认证与会话**统一由 `weaver-e10-shared` 共享规则和 `weaver-work-cli auth ...` 命令体系托管**,禁止自建或绕过登录流程。
34
+
35
+ 所有 E10 请求由 `weaver-work-cli` 统一注入以下三项请求头,缺一不可(唯一权威声明见 [`../weaver-e10-shared/SKILL.md`](../weaver-e10-shared/SKILL.md) 的「请求头契约」章节,本技能只做指针引用,不复制其代码块):
36
+
37
+ | 请求头 | 取值 | 约束 |
38
+ | --- | --- | --- |
39
+ | `Cookie` | `weaver-work-cli` 登录后返回的完整原始 Cookie 串 | 必须原样携带,禁止裁剪 / 去重 / 改写 |
40
+ | `eteamsid` | 登录会话对应的 ETEAMSID | 必须原样携带 |
41
+ | `User-Agent` | `AgentType=<agentType>,IsAgent=true` | 必须携带,不得省略 |
42
+
43
+ 以上三项均由 CLI 注入,不要手工拼装请求头,也不得把字段写进业务入参。
44
+
45
+ 认证和登录态只能按共享规则通过 `weaver-work-cli auth ...` 命令判断;禁止直接读取、列出、打印或解析用户主目录下的旧 `.e10-cli`、auth、config 或 Keychain 数据。
46
+
47
+ Agent 不得自行拼接、改写或向用户索取上述凭证(禁止要求用户贴 Cookie / ETEAMSID / Token),也不得直接 `curl` / `fetch` E10 接口,或把接口原始路径、Cookie、ETEAMSID、Token 当作"用户可执行步骤"写进回复。
48
+
49
+ 所有命令通过 `weaver-work-cli --json project run <operation>` 执行:简单 JSON 优先传 `--input-json '<json>'`,复杂或多行 JSON 先写入 UTF-8 文件再传 `--input <path>`,只有确认当前 shell 能稳定传管道时才使用 `--input -`。调用前先按需读取 references 下对应的文件查参数结构,不要猜字段;**references 是第一信息源**,`weaver-work-cli project schema` 是 operation、字段和风险等级的合约来源。
50
+
51
+ 命令示例、提示词和临时说明必须同时兼容 Windows 和 macOS/Linux;涉及 JSON 输入、用户目录、路径分隔符、Python 启动器或文件比对时,必须同时给 Windows PowerShell 与 macOS/Linux(bash/zsh)两套示例。Agent 先根据当前系统和 shell 选择对应示例,不确定时用 `node -p "process.platform"` 判断。Windows/PowerShell 下不要使用 `printf`、`$HOME/...`、`~/...`、bash 反斜杠续行、`rm/ls/diff/python3` 等 Unix-only 写法。
52
+
29
53
  ## 路由
30
54
 
31
55
  命中以下意图时按表路由到对应 operation 和 reference;不要为了普通查询执行 `project schema`、`project.version.check`、`project.form.fields` 或读取非必要 reference。
@@ -40,6 +64,7 @@ cliHelp: "weaver-work-cli project --help"
40
64
  | 按姓名解析员工/部门/分部 | `project.employee/department/subcompany.resolve` | [field-resolution.md](references/field-resolution.md) | 多个匹配必须让用户选择 |
41
65
  | 版本异常诊断 | `project.version.check` | [operations.md](references/operations.md) | 仅接口异常或用户主动询问 |
42
66
  | 创建/修改/删除任务或项目 | `project.task/*`、`project.project/*` 的 prepare/apply | [write-operations.md](references/write-operations.md) | 严格 prepare → apply |
67
+ | 附件上传、字段自动解析、系统状态改写等未暴露能力 | 无可用命令 | [disabled-capabilities.md](references/disabled-capabilities.md) | 如实告知并给替代路径,不要猜 operation |
43
68
 
44
69
  ## 高优先级语义
45
70
 
@@ -58,6 +83,14 @@ cliHelp: "weaver-work-cli project --help"
58
83
  5. 写操作先 `prepare`,向用户摘要目标、差异、风险、默认值和不可逆性;用户明确确认后才 `apply`。
59
84
  6. `partial/write_uncertain`、登录失效、回查失败或网络中断时停止写流程,不自动重试。
60
85
 
86
+ ## 输出与展示
87
+
88
+ - 列表和详情默认只给摘要:记录数 + 下方固定展示列,不粘贴完整 JSON,不罗列全部原始字段;完整响应过长时先落本地 UTF-8 文件再给摘要与路径。
89
+ - 默认展示列以 [`operations.md`](references/operations.md) 的「输出处理」为准:任务列表 `task_name / task_type / plan_start_time / plan_finish_time / manager / task_status / progress / create_time`;项目列表把 `task_*` 换成 `proj_*`;详情在此基础上补 `project`/`priority` 与描述、创建时间。用户明确指定字段时以用户为准。
90
+ - 记录名称列(`task_name` / `proj_name`)渲染为 Markdown 链接 `[名称](detail_url)`,**不单独输出 `detail_url` 列**;`detail_url_warning` 时名称保持纯文本并如实说明,不得编造 URL。
91
+ - 关联字段(`manager`、`participant(s)`、`task_type`、`proj_status` 等)只展示 `name`,不展示内部 ID;字典 ID、对象 ID、`groupId` 只用于入参。
92
+ - 未指定状态时先说明默认口径「只查未开始 + 进行中」(默认系统状态口径见 [`operations.md`](references/operations.md));返回条数等于 `pageSize` 时提示可能还有下一页。
93
+
61
94
  ## 执行原则
62
95
 
63
96
  - 业务调用统一使用 `weaver-work-cli --json project run <operation>`,优先 `--input-json`;复杂多行 JSON 用 UTF-8 文件和 `--input <path>`。
@@ -75,10 +108,42 @@ cliHelp: "weaver-work-cli project --help"
75
108
  4. 登录失效:按共享规则处理登录,不得使用旧 continuation 直接重试。
76
109
  5. 回查失败:向用户说明结果不确定,等待用户决策。
77
110
 
111
+ ## 常用命令示例
112
+
113
+ 以下示例只示意参数形态;执行前必须先按 [`../weaver-e10-shared/SKILL.md`](../weaver-e10-shared/SKILL.md) 判定是否补 `--is-connector true`,命中时本任务内每条命令都要带。
114
+
115
+ Windows PowerShell:
116
+
117
+ ```powershell
118
+ weaver-work-cli --json project run project.task.list --input-json '{"filters":{"manager":"@me"},"pageNo":1,"pageSize":10,"withDetailUrl":false,"fields":["task_name","task_type","task_status","manager","plan_finish_time","progress"]}'
119
+ weaver-work-cli --json project run project.project.list --input-json '{"pageNo":1,"pageSize":10}'
120
+ weaver-work-cli --json project run project.task.detail --input-json '{"id":"<taskId>"}'
121
+ weaver-work-cli --json project run project.dictionary.task-status --input-json '{"pageNo":1,"pageSize":100}'
122
+ weaver-work-cli --json project run project.task.create.prepare --input-json '{"records":[{"task_name":"需求评审"}]}'
123
+ ```
124
+
125
+ macOS/Linux(bash/zsh):
126
+
127
+ ```bash
128
+ weaver-work-cli --json project run project.task.list --input-json '{"filters":{"manager":"@me"},"pageNo":1,"pageSize":10,"withDetailUrl":false,"fields":["task_name","task_type","task_status","manager","plan_finish_time","progress"]}'
129
+ weaver-work-cli --json project run project.project.list --input-json '{"pageNo":1,"pageSize":10}'
130
+ weaver-work-cli --json project run project.task.detail --input-json '{"id":"<taskId>"}'
131
+ weaver-work-cli --json project run project.dictionary.task-status --input-json '{"pageNo":1,"pageSize":100}'
132
+ weaver-work-cli --json project run project.task.create.prepare --input-json '{"records":[{"task_name":"需求评审"}]}'
133
+ ```
134
+
135
+ 写操作的 `prepare` 返回 continuation 后,必须先向用户摘要再执行 apply:
136
+
137
+ ```json
138
+ {"continuation":"PREPARE_CONTINUATION","confirm":true}
139
+ ```
140
+
78
141
  ## 不在范围
79
142
 
80
- - 禁止绕过 `weaver-work-cli` 直接调用 E10 原始接口。
81
- - 不提供附件上传;`attachment` 只接受已上传文件 ID,且处理文件前必须取得用户敏感数据确认。
143
+ - 禁止绕过 `weaver-work-cli` 直接调用 E10 原始接口;禁止 `curl` / `fetch` / 浏览器自动化访问 `/api/ebuilder/**`、`/api/ebproject/**` 等路径。
144
+ - 禁止读取或复制 CLI runtime 内部的 Token、Cookie、ETEAMSID;禁止读取、列出、打印或解析用户主目录下的旧 `.e10-cli`、auth、config 或 Keychain 数据。
145
+ - 不提供附件上传;`attachment` 只接受已上传文件 ID,且处理文件前必须取得用户敏感数据确认。未暴露能力清单见 [`disabled-capabilities.md`](references/disabled-capabilities.md)。
82
146
  - 不自动替用户选择同名人员、组织、项目、任务或字典选项。
83
147
  - 不自动翻页、自动扩大 pageSize 或自动聚合全量数据。
84
148
  - 不在正常路径预调用版本诊断。
149
+ - 非事井然的 E10 能力(流程审批、组织人员、发票、文书定档案等)不由本 Skill 承载,转对应业务技能。
@@ -0,0 +1,22 @@
1
+ # 事井然未暴露能力
2
+
3
+ ## 何时使用
4
+
5
+ 用户要求的动作在本 Skill 的 operation 列表里没有对应命令时,先读本文,如实说明「当前 CLI 未提供该能力」,并给出可行的替代路径;**不要猜测 operation 名,不要直接调用 E10 原始接口,也不要用别的 operation 硬凑**。
6
+
7
+ ## 当前未纳入 CLI 的能力
8
+
9
+ | 用户诉求 | 现状 | 处理方式 |
10
+ | --- | --- | --- |
11
+ | 给任务/项目上传或替换附件 | `project.attachment.upload` 未暴露。源资料只说明 `attachment` 字段接受**已上传文件 ID**,未提供事井然模块自己的上传接口与文件安全确认链 | 不要把本地文件当作附件直接写入。先告知用户需在 E10 页面完成上传,再提供已上传文件 ID;本 Skill 不代办上传 |
12
+ | 按字段类型自动解析名称到 ID | `project.field.auto-resolve` 未暴露。CLI 只提供分步解析 operation,由 Agent 按字段类型显式编排 | 按 [`field-resolution.md`](field-resolution.md) 分步解析:人员/部门/分部用 `project.employee/department/subcompany.resolve`;项目/任务名称用 `project.project.list` / `project.task.list`;Ebuilder 关联字段用 `project.form.fields` + `project.form.data.page`;字典字段用 `project.dictionary.*` |
13
+ | 修改系统状态 `sys_status` | `sys_status` 是系统维护字段,只能用于查询过滤 | 不接受写入。用户要变更系统状态时,说明需在 E10 页面操作 |
14
+ | 跨表单批量导入 / 批量删除 | 未暴露 | 创建可用 `records` 数组在单次 `create.prepare` 里传 1-100 条;删除只能逐条 `delete.prepare/apply`,不要并发刷接口 |
15
+ | 直接把某个字段的全部选项元数据一次性拉全 | `project.form.options` 可用,但必须先确定 `table` 与 `fieldName` | 按 [`field-resolution.md`](field-resolution.md) 的「Select 与字典字段」先定位字段,再查选项 |
16
+ | 附件内容下载、OCR、文件内容解析 | 未暴露 | 不属于本 Skill;不要用其他模块的接口代替 |
17
+
18
+ ## 注意
19
+
20
+ - 未暴露 ≠ 用户不能做:优先给出 E10 页面路径或已支持的分步替代方案,不要为了"完成"而编造 operation、字段或接口路径。
21
+ - 任何写入都必须走 `prepare -> apply` 确认链;不用"能力未暴露"当作跳过用户确认的理由。
22
+ - 与 operation、字段、风险等级有关的最终依据是 `weaver-work-cli project schema`,不是本文;本文只说明缺口与替代路径。
@@ -1241,7 +1241,7 @@
1241
1241
  }
1242
1242
  },
1243
1243
  "artifactBaseline": {
1244
- "aggregateSha256": "60eafebefaa0b7d6e75758ae78e3e5e1349ee453f33826de54c7293fb6e0fa41",
1244
+ "aggregateSha256": "eb2413bba08ce89d006615d49d08ef5967ba2220d11481e674dafd78680100c8",
1245
1245
  "files": [
1246
1246
  {
1247
1247
  "path": "docs/_catalog.md",
@@ -1288,8 +1288,8 @@
1288
1288
  {
1289
1289
  "path": "skills/weaver-e10-shijingran/SKILL.md",
1290
1290
  "role": "generated-skill-entry",
1291
- "size": 8174,
1292
- "sha256": "597968607ca3321aac3b91e687c5963bdf1baee16d5f394aad64910e1ece53bf"
1291
+ "size": 14475,
1292
+ "sha256": "4d92d0dda6c2f30b7501dcbc2e5d1b9e42b69a22d73b2df8d0c6a61326821154"
1293
1293
  },
1294
1294
  {
1295
1295
  "path": "src/shortcuts/index.ts",
@@ -1368,6 +1368,12 @@
1368
1368
  "role": "generated-test",
1369
1369
  "size": 15966,
1370
1370
  "sha256": "27abe41e8c6819d48c926fc10ebfaee7653defc8672c3634d0e838d42cde7f35"
1371
+ },
1372
+ {
1373
+ "path": "skills/weaver-e10-shijingran/references/disabled-capabilities.md",
1374
+ "role": "generated-reference",
1375
+ "size": 2501,
1376
+ "sha256": "722879e32b17988297f5c7ee9eab2f6df056e9da2a1560b1bdba3507b13d08b1"
1371
1377
  }
1372
1378
  ]
1373
1379
  },
@@ -1375,6 +1381,8 @@
1375
1381
  "只记录源资料指纹、operation 影响索引和产物指纹,不保存 Cookie、Token、认证信息、真实内网地址、大段源正文或个人主目录路径。",
1376
1382
  "source-manifest.json 自身不纳入 artifactBaseline 文件列表,避免自引用指纹无法稳定计算。",
1377
1383
  "源资料版本为 1.0.4,覆盖 25 个接口文档、1 个目录文档和 1 个字段解析流程指引。",
1378
- "project.attachment.upload 与 project.field.auto-resolve 因源资料缺少安全实现契约而保留为 withheldOperations。"
1384
+ "project.attachment.upload 与 project.field.auto-resolve 因源资料缺少安全实现契约而保留为 withheldOperations。",
1385
+ "2026-09-24:刷新本技能包内产物指纹(SKILL.md / references),新增 references/disabled-capabilities.md;行尾统一为 LF。",
1386
+ "2026-10-08:SKILL.md 的「登录与请求头凭证契约」改为请求头声明表格(Cookie / eteamsid / User-Agent),刷新本包产物指纹。"
1379
1387
  ]
1380
1388
  }