weaver-work-cli 0.1.4 → 0.1.5

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 (87) hide show
  1. package/dist/internal/e10/request-runtime.js +3 -2
  2. package/dist/shortcuts/index.js +4 -0
  3. package/dist/shortcuts/meeting/continuation.js +70 -0
  4. package/dist/shortcuts/meeting/errors.js +63 -0
  5. package/dist/shortcuts/meeting/host.js +167 -0
  6. package/dist/shortcuts/meeting/index.js +123 -0
  7. package/dist/shortcuts/meeting/manifest.js +64 -0
  8. package/dist/shortcuts/meeting/operations/catalogs.js +58 -0
  9. package/dist/shortcuts/meeting/operations/change.js +242 -0
  10. package/dist/shortcuts/meeting/operations/conflicts.js +142 -0
  11. package/dist/shortcuts/meeting/operations/create.js +349 -0
  12. package/dist/shortcuts/meeting/operations/env.js +191 -0
  13. package/dist/shortcuts/meeting/operations/lifecycle.js +139 -0
  14. package/dist/shortcuts/meeting/operations/meetings.js +73 -0
  15. package/dist/shortcuts/meeting/operations/receipts.js +195 -0
  16. package/dist/shortcuts/meeting/operations/registry.js +47 -0
  17. package/dist/shortcuts/meeting/operations/rooms.js +132 -0
  18. package/dist/shortcuts/meeting/operations/shared.js +112 -0
  19. package/dist/shortcuts/meeting/operations/signs.js +41 -0
  20. package/dist/shortcuts/meeting/operations/types.js +25 -0
  21. package/dist/shortcuts/meeting/operations.js +19 -0
  22. package/dist/shortcuts/yimiaoban/continuation.js +70 -0
  23. package/dist/shortcuts/yimiaoban/errors.js +61 -0
  24. package/dist/shortcuts/yimiaoban/host.js +469 -0
  25. package/dist/shortcuts/yimiaoban/index.js +271 -0
  26. package/dist/shortcuts/yimiaoban/manifest.js +28 -0
  27. package/dist/shortcuts/yimiaoban/operations/file-user.js +288 -0
  28. package/dist/shortcuts/yimiaoban/operations/group-read.js +477 -0
  29. package/dist/shortcuts/yimiaoban/operations/group-write.js +591 -0
  30. package/dist/shortcuts/yimiaoban/operations/msg-parse.js +429 -0
  31. package/dist/shortcuts/yimiaoban/operations/msg-sync.js +519 -0
  32. package/dist/shortcuts/yimiaoban/operations/msg-write.js +593 -0
  33. package/dist/shortcuts/yimiaoban/operations/person.js +102 -0
  34. package/dist/shortcuts/yimiaoban/operations/registry.js +38 -0
  35. package/dist/shortcuts/yimiaoban/operations/session.js +437 -0
  36. package/dist/shortcuts/yimiaoban/operations/shared.js +236 -0
  37. package/dist/shortcuts/yimiaoban/operations/types.js +27 -0
  38. package/dist/shortcuts/yimiaoban/operations/write-util.js +78 -0
  39. package/dist/shortcuts/yimiaoban/operations.js +8 -0
  40. package/docs/_catalog.md +6 -0
  41. package/docs/meeting.md +48 -0
  42. package/docs/yimiaoban.md +86 -0
  43. package/package.json +1 -1
  44. package/skill-template/business-info.json +4 -1
  45. package/skill-template/domains/meeting.md +3 -0
  46. package/skill-template/domains/yimiaoban.md +21 -0
  47. package/skills/weaver-e10-calendar/SKILL.md +1 -0
  48. package/skills/weaver-e10-esb/SKILL.md +1 -0
  49. package/skills/weaver-e10-esb/references/source-manifest.json +3 -3
  50. package/skills/weaver-e10-hrm/SKILL.md +1 -0
  51. package/skills/weaver-e10-jiuchuanhui/SKILL.md +1 -0
  52. package/skills/weaver-e10-jucailin/SKILL.md +1 -0
  53. package/skills/weaver-e10-mail/SKILL.md +1 -0
  54. package/skills/weaver-e10-meeting/SKILL.md +106 -0
  55. package/skills/weaver-e10-meeting/product.json +8 -0
  56. package/skills/weaver-e10-meeting/references/meeting-change.md +67 -0
  57. package/skills/weaver-e10-meeting/references/meeting-create.md +52 -0
  58. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +57 -0
  59. package/skills/weaver-e10-meeting/references/meeting-queries.md +61 -0
  60. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +48 -0
  61. package/skills/weaver-e10-meeting/references/meeting-update.md +7 -0
  62. package/skills/weaver-e10-meeting/references/source-manifest.json +50 -0
  63. package/skills/weaver-e10-plan/SKILL.md +1 -0
  64. package/skills/weaver-e10-plan/references/source-manifest.json +3 -3
  65. package/skills/weaver-e10-qiyecheng/SKILL.md +1 -0
  66. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +3 -3
  67. package/skills/weaver-e10-shared/SKILL.md +4 -0
  68. package/skills/weaver-e10-skill-maker/SKILL.md +1 -0
  69. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
  70. package/skills/weaver-e10-wenshuding/SKILL.md +1 -0
  71. package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
  72. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +5 -5
  73. package/skills/weaver-e10-yimiaoban/SKILL.md +104 -0
  74. package/skills/weaver-e10-yimiaoban/product.json +8 -0
  75. package/skills/weaver-e10-yimiaoban/references/ding-write.md +68 -0
  76. package/skills/weaver-e10-yimiaoban/references/error-codes.md +85 -0
  77. package/skills/weaver-e10-yimiaoban/references/field-resolution.md +46 -0
  78. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +54 -0
  79. package/skills/weaver-e10-yimiaoban/references/group-read.md +55 -0
  80. package/skills/weaver-e10-yimiaoban/references/group-write.md +72 -0
  81. package/skills/weaver-e10-yimiaoban/references/msg-read.md +59 -0
  82. package/skills/weaver-e10-yimiaoban/references/msg-write.md +64 -0
  83. package/skills/weaver-e10-yimiaoban/references/person.md +44 -0
  84. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +61 -0
  85. package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +47 -0
  86. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +485 -0
  87. package/skills/weaver-e10-ziguanjia/SKILL.md +1 -0
@@ -0,0 +1,102 @@
1
+ import { ymbValidationError } from '../errors.js';
2
+ import { READ, schemaOf, ymbSuccess, } from './types.js';
3
+ import { splitIds } from './shared.js';
4
+ /** 姓名 → 人员解析(数据采集走 hrm,不用 IM 群成员姓名接口)。 */
5
+ const personResolve = {
6
+ name: 'yimiaoban.person.resolve',
7
+ resource: 'person',
8
+ method: 'person.resolve',
9
+ action: 'yimiaoban.person.resolve',
10
+ summary: '按姓名/工号/手机/邮箱模糊查询在职员工,返回 uid/cid/部门等(供发消息与成员解析)',
11
+ capability: 'person',
12
+ risk: READ,
13
+ inputSchema: schemaOf({
14
+ name: { type: 'string', minLength: 1, description: '姓名(模糊)/工号/手机/邮箱' },
15
+ }, { required: ['name'], description: '人员解析:输入一个查询关键字' }),
16
+ handler: async (args, { host }) => {
17
+ const name = String(args.name ?? '').trim();
18
+ if (!name)
19
+ throw ymbValidationError('name_required', 'name 必填');
20
+ const employees = await host.searchEmployeeByName(name);
21
+ const rows = employees.map((e) => ({
22
+ uid: e.uid,
23
+ name: e.name,
24
+ department: e.department,
25
+ departmentId: e.departmentId,
26
+ subCompany: e.subCompany,
27
+ position: e.position,
28
+ jobNum: e.jobNum,
29
+ mobile: e.mobile,
30
+ cid: e.cid,
31
+ ok: Boolean(e.uid && e.cid && e.uid !== '0' && e.cid !== '0'),
32
+ }));
33
+ return ymbSuccess('yimiaoban.person.resolve', {
34
+ total: rows.length,
35
+ keyword: name,
36
+ employees: rows,
37
+ });
38
+ },
39
+ };
40
+ /** uid 批量解析(emp-by-ids 语义)。 */
41
+ const personResolveByIds = {
42
+ name: 'yimiaoban.person.resolveByIds',
43
+ resource: 'person',
44
+ method: 'person.resolveByIds',
45
+ action: 'yimiaoban.person.resolveByIds',
46
+ summary: '按 uid 列表批量解析人员姓名/部门/岗位/cid(一次最多 100,超出自动分批)',
47
+ capability: 'person',
48
+ risk: READ,
49
+ inputSchema: schemaOf({
50
+ ids: { type: 'array', items: { type: 'string' }, description: '人员 uid 列表' },
51
+ }, { required: ['ids'], description: '按 id 批量解析人员' }),
52
+ handler: async (args, { host }) => {
53
+ const ids = Array.isArray(args.ids) ? args.ids.map((x) => String(x)).filter(Boolean) : [];
54
+ if (ids.length === 0)
55
+ throw ymbValidationError('ids_required', 'ids 不能为空');
56
+ const map = await host.resolveUsersByIds(ids);
57
+ return ymbSuccess('yimiaoban.person.resolveByIds', {
58
+ requested: ids.length,
59
+ resolved: Object.keys(map).length,
60
+ employees: ids
61
+ .map((uid) => map[uid])
62
+ .filter(Boolean)
63
+ .map((u) => ({
64
+ uid: u.uid,
65
+ name: u.name,
66
+ department: u.department,
67
+ departmentId: u.departmentId,
68
+ subCompany: u.subCompany,
69
+ position: u.position,
70
+ jobNum: u.jobNum,
71
+ mobile: u.mobile,
72
+ cid: u.cid,
73
+ ok: Boolean(u.uid && u.cid && u.uid !== '0' && u.cid !== '0'),
74
+ })),
75
+ });
76
+ },
77
+ };
78
+ /** 成员 uid 列表 → [{cid, uid}],失败者进 failed(cid 来自成员自身 hrm 解析,可能跨团队)。 */
79
+ async function resolveMemberUsers(uidListRaw, host) {
80
+ const uidArr = Array.isArray(uidListRaw)
81
+ ? uidListRaw.map((x) => String(x)).filter(Boolean)
82
+ : splitIds(uidListRaw, 'users');
83
+ const map = await host.resolveUsersByIds(uidArr);
84
+ const users = [];
85
+ const failed = [];
86
+ for (const uid of uidArr) {
87
+ const u = map[uid];
88
+ const cid = u?.cid ?? '';
89
+ const uidOk = uid !== '' && uid !== '0';
90
+ if (!uidOk || !cid || cid === '0') {
91
+ failed.push({ uid, name: u?.name || '' });
92
+ continue;
93
+ }
94
+ users.push({ cid, uid });
95
+ }
96
+ return { users, failed };
97
+ }
98
+ export { resolveMemberUsers };
99
+ export const personOperations = [
100
+ personResolve,
101
+ personResolveByIds,
102
+ ];
@@ -0,0 +1,38 @@
1
+ import { ymbValidationError } from '../errors.js';
2
+ import { groupReadOperations } from './group-read.js';
3
+ import { groupWriteOperations } from './group-write.js';
4
+ import { msgReadOperations } from './msg-sync.js';
5
+ import { msgWriteOperations } from './msg-write.js';
6
+ import { sessionOperations } from './session.js';
7
+ import { personOperations } from './person.js';
8
+ import { fileUserOperations } from './file-user.js';
9
+ function buildRegistry() {
10
+ const registry = new Map();
11
+ const definitions = [
12
+ ...personOperations,
13
+ ...msgReadOperations,
14
+ ...msgWriteOperations,
15
+ ...groupReadOperations,
16
+ ...groupWriteOperations,
17
+ ...sessionOperations,
18
+ ...fileUserOperations,
19
+ ];
20
+ for (const definition of definitions) {
21
+ if (registry.has(definition.name)) {
22
+ throw new Error(`duplicate yimiaoban operation: ${definition.name}`);
23
+ }
24
+ registry.set(definition.name, definition);
25
+ }
26
+ return registry;
27
+ }
28
+ const ymbOperationRegistry = buildRegistry();
29
+ export function ymbOperationDefinitions() {
30
+ return [...ymbOperationRegistry.values()];
31
+ }
32
+ export function getYmbOperation(operation) {
33
+ const definition = ymbOperationRegistry.get(operation);
34
+ if (!definition) {
35
+ throw ymbValidationError('operation_unknown', `易秒办不支持操作:${operation}`);
36
+ }
37
+ return definition;
38
+ }
@@ -0,0 +1,437 @@
1
+ import { ymbValidationError } from '../errors.js';
2
+ import { READ, schemaOf, ymbSuccess, } from './types.js';
3
+ import { assertImOk, formatFullTime, parseMsg, resolveAuthUid, str, toUint, unwrapInner, } from './shared.js';
4
+ import { collapseNewlines, escapePipe, extractFiles, extractImgs, extractVideos, parseContent, senderName, } from './msg-parse.js';
5
+ const MAX_MSG_ID = String((1n << 63n) - 1n);
6
+ const DEFAULT_PAGE_SIZE = 20;
7
+ const MAX_PAGE_SIZE = 50;
8
+ /** 会话类型归类。 */
9
+ export function sessionTypeName(type) {
10
+ if ([1, 6, 9].includes(type))
11
+ return '单聊';
12
+ if ([2, 3, 7, 11].includes(type))
13
+ return '群聊';
14
+ if ([4, 10].includes(type))
15
+ return '系统';
16
+ return '其它';
17
+ }
18
+ /** 会话列表。 */
19
+ const sessionList = {
20
+ name: 'yimiaoban.session.list',
21
+ resource: 'session',
22
+ method: 'session.list',
23
+ action: 'yimiaoban.session.list',
24
+ summary: '拉取当前用户会话列表(单聊/群聊/系统消息,解析名称与副标题)',
25
+ capability: 'session',
26
+ risk: READ,
27
+ inputSchema: schemaOf({
28
+ num: { type: 'integer', minimum: 1, maximum: 50, description: '单页条数(默认 20)' },
29
+ msgid: { type: 'string', description: '锚点消息 id(默认 int64 max 从最新)' },
30
+ flag: { type: 'integer', enum: [0, 1, 2], description: '0 up+down / 1 up / 2 down(默认 2)' },
31
+ type: { type: 'integer', description: '仅展示该 session_type' },
32
+ around: { type: 'boolean', description: '附近语义(flag=0 且 up/down 各半)' },
33
+ }, { description: '拉取会话列表' }),
34
+ handler: async (args, { host }) => {
35
+ const num = Math.min(toUint(args.num, 'num') ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
36
+ const flag = args.flag !== undefined ? Number(args.flag) : 2;
37
+ const around = args.around === true;
38
+ const curAnchor = args.msgid !== undefined && args.msgid !== '' ? String(args.msgid) : MAX_MSG_ID;
39
+ const isNewestAnchor = curAnchor === MAX_MSG_ID;
40
+ const splitUpDown = around || (flag === 0 && !isNewestAnchor);
41
+ const half = Math.floor(num / 2);
42
+ const downSize = splitUpDown ? Math.max(half, 1) : num;
43
+ const upSize = splitUpDown ? Math.max(num - half, 1) : num;
44
+ const body = {
45
+ msgid: curAnchor,
46
+ flag,
47
+ view: 0,
48
+ sview: 0,
49
+ reminder: 0,
50
+ top: 1,
51
+ };
52
+ if (flag !== 2)
53
+ body.up = { page_size: upSize, msgid: '0' };
54
+ if (flag !== 1)
55
+ body.down = { page_size: downSize, msgid: '0' };
56
+ const res = await host.request({
57
+ method: 'POST',
58
+ path: '/api/em/msg/executeIm/data/getSessionContext',
59
+ body,
60
+ operation: 'yimiaoban.session.list',
61
+ });
62
+ assertImOk(res, '拉取会话列表');
63
+ const inner = unwrapInner(res);
64
+ const sessions = [...(inner?.down_datas || []), ...(inner?.up_datas || [])];
65
+ // 批量收集需要解析的对象:单聊对方 fuser / 群 id / 系统 group id
66
+ const singleTargets = new Map();
67
+ const groupIds = new Set();
68
+ const sysGroupIds = new Set();
69
+ for (const s of sessions) {
70
+ const type = Number(s?.session_type ?? -1);
71
+ if ([1, 6, 9].includes(type) && s?.fuser?.uid) {
72
+ singleTargets.set(String(s.fuser.uid), { cid: str(s.fuser.cid), uid: str(s.fuser.uid) });
73
+ }
74
+ else if ([2, 3, 7, 11].includes(type) && s?.group_id) {
75
+ groupIds.add(String(s.group_id));
76
+ }
77
+ else if ([4, 10].includes(type) && s?.group) {
78
+ sysGroupIds.add(Number(s.group));
79
+ }
80
+ }
81
+ const [personMap, groupRes] = await Promise.all([
82
+ host.resolveUsersByIds([...singleTargets.keys()]),
83
+ groupIds.size ? host.request({ method: 'POST', path: '/api/em/msg/executeIm/group/queryGroupInfo', body: { trans_id: 0, type: 0, mask: 0, group_id: [...groupIds], flag: 0 }, operation: 'yimiaoban.session.list.groups' }) : Promise.resolve(null),
84
+ ]);
85
+ const groupInfoMap = new Map();
86
+ if (groupRes) {
87
+ const gi = unwrapInner(groupRes)?.datas || [];
88
+ for (const g of gi)
89
+ if (g?.name)
90
+ groupInfoMap.set(str(g.group_id ?? g.id), g.name);
91
+ }
92
+ const sysNameMap = new Map();
93
+ if (sysGroupIds.size) {
94
+ const sysRes = await host.request({
95
+ method: 'POST',
96
+ path: '/api/em/msg/executeIm/system-msg/syncGroupType',
97
+ body: { gids: [...sysGroupIds], tids: [], lang: 'zh_CN', showdel: 1 },
98
+ operation: 'yimiaoban.session.list.sysgroups',
99
+ });
100
+ const sg = unwrapInner(sysRes)?.groups || [];
101
+ for (const g of sg)
102
+ if (g?.groupName)
103
+ sysNameMap.set(Number(g.groupId), g.groupName);
104
+ }
105
+ const rows = [];
106
+ for (const s of sessions) {
107
+ const type = Number(s?.session_type ?? -1);
108
+ const typeName = sessionTypeName(type);
109
+ let name = '';
110
+ if ([1, 6, 9].includes(type)) {
111
+ const target = singleTargets.get(str(s?.fuser?.uid));
112
+ name = target ? (personMap[target.uid]?.name || target.uid) : str(s?.name || s?.title || '');
113
+ }
114
+ else if ([2, 3, 7, 11].includes(type)) {
115
+ name = groupInfoMap.get(str(s?.group_id)) || str(s?.group_id);
116
+ }
117
+ else if ([4, 10].includes(type)) {
118
+ name = sysNameMap.get(Number(s?.group)) || str(s?.group);
119
+ }
120
+ else {
121
+ name = str(s?.name || s?.title || '');
122
+ }
123
+ // 未读数取 unread_count;免打扰取 reminder=1(消息接收但不提醒);时间取会话更新时间 update_tm
124
+ const unread = s?.unread_count ?? s?.unread ?? 0;
125
+ const dontDisturb = Number(s?.reminder ?? s?.reminder_type ?? 0) === 1;
126
+ // 副标题 = 发送者:内容(从 title 解析)
127
+ const titleRaw = s?.title;
128
+ const titleObj = typeof titleRaw === 'string' ? parseMsg(titleRaw) : (titleRaw && typeof titleRaw === 'object' ? titleRaw : null);
129
+ let subtitleSender = '';
130
+ let subtitleContent = '';
131
+ let subtitleMedia = '';
132
+ if (titleObj) {
133
+ const parsed = parseContent(titleObj, { i18nMap: {}, nameMap: {} });
134
+ const sname = str(titleObj.sname);
135
+ if (sname)
136
+ subtitleSender = sname;
137
+ else if (titleObj.scid || titleObj.suid) {
138
+ const hit = await host.resolveUsersByIds([str(titleObj.suid)]);
139
+ subtitleSender = hit[str(titleObj.suid)]?.name || str(titleObj.suid);
140
+ }
141
+ const imgs = extractImgs(titleObj);
142
+ const videos = extractVideos(titleObj);
143
+ const files = extractFiles(titleObj);
144
+ if (imgs.length)
145
+ subtitleMedia = `[图片]${imgs[0].name}`;
146
+ else if (videos.length)
147
+ subtitleMedia = `[视频]${videos[0].name}`;
148
+ else if (files.length)
149
+ subtitleMedia = `[文件]${files[0].name}`;
150
+ const mustFlag = Number(titleObj.tf ?? 0) & 0x01;
151
+ const prefix = mustFlag ? '[必达]' : '';
152
+ subtitleContent = subtitleMedia ? `${prefix}${subtitleMedia}` : `${prefix}${collapseNewlines(parsed.content)}`;
153
+ }
154
+ else {
155
+ subtitleContent = collapseNewlines(String(titleRaw ?? ''));
156
+ }
157
+ const subtitle = subtitleSender ? `${subtitleSender}:${subtitleContent}` : subtitleContent;
158
+ rows.push({
159
+ sessionId: str(s?.session_id ?? s?.id ?? ''),
160
+ sessionType: type,
161
+ sessionTypeName: typeName,
162
+ name,
163
+ unread: Number(unread),
164
+ dontDisturb,
165
+ unreadLabel: Number(unread) > 0 ? (dontDisturb ? `${unread}(免打扰)` : String(unread)) : '',
166
+ time: formatFullTime(s?.update_tm ?? s?.update_time ?? s?.last_time ?? s?.create),
167
+ subtitle,
168
+ lastMsgid: str(s?.last_msgid ?? ''),
169
+ });
170
+ }
171
+ rows.sort((a, b) => (BigInt(a.lastMsgid || '0') < BigInt(b.lastMsgid || '0') ? 1 : -1));
172
+ const filterType = args.type !== undefined ? Number(args.type) : undefined;
173
+ const filtered = filterType !== undefined ? rows.filter((r) => r.sessionType === filterType) : rows;
174
+ const minLast = filtered.length ? filtered[filtered.length - 1].lastMsgid : '';
175
+ return ymbSuccess('yimiaoban.session.list', {
176
+ total: filtered.length,
177
+ sessions: filtered,
178
+ nextMsgid: minLast && BigInt(minLast) > 0n ? minLast : undefined,
179
+ hasMore: filtered.length > 0,
180
+ pageSize: num,
181
+ });
182
+ },
183
+ };
184
+ /** 系统消息分组配置搜索。 */
185
+ const sysmsgGroupSearch = {
186
+ name: 'yimiaoban.sysmsg.groupSearch',
187
+ resource: 'sysmsg',
188
+ method: 'sysmsg.groupSearch',
189
+ action: 'yimiaoban.sysmsg.groupSearch',
190
+ summary: '按名称搜索系统消息分组/类型配置(供 querySysMsgInfo 使用)',
191
+ capability: 'sysmsg',
192
+ risk: READ,
193
+ inputSchema: schemaOf({
194
+ name: { type: 'string', minLength: 1, description: '分组/类型名称(模糊)' },
195
+ type: { type: 'integer', enum: [0, 1], description: '0 搜 group(默认)/1 搜 type' },
196
+ }, { required: ['name'], description: '搜索系统消息分组配置' }),
197
+ handler: async (args, { host }) => {
198
+ const body = {
199
+ data: {
200
+ cond: { mask: 1, groupName: String(args.name) },
201
+ lang: 'zh_CN',
202
+ page: { current: 1, pageSize: 30 },
203
+ type: Number(args.type ?? 0),
204
+ order: 0,
205
+ },
206
+ };
207
+ const res = await host.request({
208
+ method: 'POST',
209
+ path: '/api/em/msg/executeIm/system-msg/searchSysMsgGroup',
210
+ body,
211
+ operation: 'yimiaoban.sysmsg.groupSearch',
212
+ });
213
+ assertImOk(res, '搜索系统消息分组');
214
+ const inner = unwrapInner(res);
215
+ const groups = (inner?.groups || []).map((g) => ({
216
+ groupId: g.groupId,
217
+ groupName: g.groupName,
218
+ typesIds: g.typesIds || [],
219
+ i18nName: g.i18nName,
220
+ enable: g.enable,
221
+ state: g.state,
222
+ }));
223
+ const types = (inner?.types || []).map((t) => ({
224
+ typeId: t.typeId,
225
+ typeName: t.typeName,
226
+ i18nName: t.i18nName,
227
+ state: t.state,
228
+ }));
229
+ return ymbSuccess('yimiaoban.sysmsg.groupSearch', { groups, types, total: Math.max(groups.length, types.length) });
230
+ },
231
+ };
232
+ /** 系统消息分组/类型配置拉取。 */
233
+ const sysmsgTypeSync = {
234
+ name: 'yimiaoban.sysmsg.typeSync',
235
+ resource: 'sysmsg',
236
+ method: 'sysmsg.typeSync',
237
+ action: 'yimiaoban.sysmsg.typeSync',
238
+ summary: '按 gids/tids 拉取系统消息分组/类型配置',
239
+ capability: 'sysmsg',
240
+ risk: READ,
241
+ inputSchema: schemaOf({
242
+ gids: { type: 'array', items: { type: 'integer' }, description: '系统分组 id(与 tids 至少其一)' },
243
+ tids: { type: 'array', items: { type: 'integer' }, description: '消息类型 id' },
244
+ }, { description: 'gids / tids 至少其一' }),
245
+ handler: async (args, { host }) => {
246
+ const gids = Array.isArray(args.gids) ? args.gids.map((x) => Number(x)).filter((n) => Number.isInteger(n)) : [];
247
+ const tids = Array.isArray(args.tids) ? args.tids.map((x) => Number(x)).filter((n) => Number.isInteger(n)) : [];
248
+ if (!gids.length && !tids.length)
249
+ throw ymbValidationError('target_required', 'gids 与 tids 至少其一');
250
+ const body = { gids, tids, lang: 'zh_CN', showdel: 1 };
251
+ const res = await host.request({
252
+ method: 'POST',
253
+ path: '/api/em/msg/executeIm/system-msg/syncGroupType',
254
+ body,
255
+ operation: 'yimiaoban.sysmsg.typeSync',
256
+ });
257
+ assertImOk(res, '同步系统消息配置');
258
+ const inner = unwrapInner(res);
259
+ const groups = (inner?.groups || []).map((g) => ({
260
+ groupId: g.groupId,
261
+ groupName: g.groupName,
262
+ typesIds: g.typesIds || [],
263
+ i18nName: g.i18nName,
264
+ enable: g.enable,
265
+ state: g.state,
266
+ sunread: g.sunread,
267
+ }));
268
+ const types = (inner?.types || []).map((t) => ({
269
+ typeId: t.typeId,
270
+ typeName: t.typeName,
271
+ i18nName: t.i18nName,
272
+ state: t.state,
273
+ }));
274
+ return ymbSuccess('yimiaoban.sysmsg.typeSync', { groups, types });
275
+ },
276
+ };
277
+ /** 系统消息内容拉取。 */
278
+ const sysmsgQuery = {
279
+ name: 'yimiaoban.sysmsg.query',
280
+ resource: 'sysmsg',
281
+ method: 'sysmsg.query',
282
+ action: 'yimiaoban.sysmsg.query',
283
+ summary: '拉取系统消息(流程/任务/公文等),支持阅读维度/处理状态/时间范围/单页倒序',
284
+ capability: 'sysmsg',
285
+ risk: READ,
286
+ inputSchema: schemaOf({
287
+ group: { type: 'integer', minimum: 1, description: '系统分组 id' },
288
+ type: { type: 'integer', description: '消息类型 id' },
289
+ num: { type: 'integer', minimum: 1, maximum: 50, description: '单页条数(默认 20)' },
290
+ flag: { type: 'integer', enum: [0, 1, 2], description: '0 不分(默认)/1 已读/2 未读' },
291
+ deal: { type: 'integer', enum: [0, 1, 2], description: '0 全部(默认)/1 未处理/2 已处理' },
292
+ from: { type: 'string', description: '起始时间' },
293
+ to: { type: 'string', description: '结束时间' },
294
+ latest: { type: 'boolean', description: '拉最新不限日期' },
295
+ start: { type: 'string', description: '手动起始消息 id(最高优先)' },
296
+ end: { type: 'string', description: '手动结束消息 id' },
297
+ }, { required: ['group'], description: '拉取系统消息' }),
298
+ handler: async (args, { host }) => {
299
+ const group = toUint(args.group, 'group');
300
+ if (group === undefined || group <= 0) {
301
+ throw ymbValidationError('group_required', 'group(系统消息分组 id,来自 sysmsg.groupSearch 或会话列表)必填且为正整数');
302
+ }
303
+ const hasType = args.type !== undefined && args.type !== null && args.type !== '';
304
+ const msgType = { group };
305
+ if (hasType)
306
+ msgType.type = toUint(args.type, 'type');
307
+ // start/end 计算
308
+ let start = MAX_MSG_ID;
309
+ let end = '0';
310
+ if (args.start !== undefined && args.start !== null && args.start !== '') {
311
+ start = String(args.start);
312
+ end = args.end !== undefined && args.end !== '' ? String(args.end) : '0';
313
+ }
314
+ else if (args.latest !== true && (args.from || args.to)) {
315
+ const fromSec = args.from ? parseTs(args.from, false) : 0;
316
+ const toSec = args.to ? parseTs(args.to, true) : 0;
317
+ if (toSec > 0)
318
+ start = idRange(toSec).max;
319
+ if (fromSec > 0)
320
+ end = idRange(fromSec).min;
321
+ }
322
+ else if (args.latest !== true) {
323
+ // 默认当天
324
+ const now = new Date();
325
+ const from = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
326
+ const to = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
327
+ start = idRange(Math.floor(to.getTime() / 1000)).max;
328
+ end = idRange(Math.floor(from.getTime() / 1000)).min;
329
+ }
330
+ const num = Math.min(toUint(args.num, 'num') ?? 20, MAX_PAGE_SIZE);
331
+ const body = {
332
+ trans_id: 0,
333
+ dev: 4,
334
+ enc_type: 0,
335
+ flag: Number(args.flag ?? 0),
336
+ deal_status: Number(args.deal ?? 0),
337
+ datas: [{ msg_type: msgType, max_num: num, start, end }],
338
+ };
339
+ const res = await host.request({
340
+ method: 'POST',
341
+ path: '/api/em/msg/executeIm/system-msg/querySysMsgInfo',
342
+ body,
343
+ operation: 'yimiaoban.sysmsg.query',
344
+ });
345
+ assertImOk(res, '拉取系统消息');
346
+ const inner = unwrapInner(res);
347
+ const rawRows = inner?.datas || [];
348
+ // 状态模式:查 group 配置的 sunread(1=处理状态 / 0=阅读状态)
349
+ let dealMode = false;
350
+ try {
351
+ const cfgRes = await host.request({
352
+ method: 'POST',
353
+ path: '/api/em/msg/executeIm/system-msg/syncGroupType',
354
+ body: { gids: [group], tids: [], lang: 'zh_CN', showdel: 1 },
355
+ operation: 'yimiaoban.sysmsg.query.cfg',
356
+ });
357
+ const cfg = unwrapInner(cfgRes)?.groups?.[0];
358
+ dealMode = Number(cfg?.sunread) === 1;
359
+ }
360
+ catch { /* 配置获取失败按阅读模式处理 */ }
361
+ const msgRows = rawRows.map((r) => ({
362
+ msg: r?.msg,
363
+ ser_msgid: r?.ser_msgid,
364
+ from_uid: r?.from_uid,
365
+ msgtime: r?.msgtime,
366
+ read_status: r?.read_status,
367
+ deal_status: r?.deal_status,
368
+ }));
369
+ // 富化
370
+ const { collectI18nIds, collectPersonIds } = await import('./msg-parse.js');
371
+ const i18nIds = collectI18nIds(msgRows);
372
+ const personIds = collectPersonIds(msgRows);
373
+ for (const r of msgRows)
374
+ if (r.from_uid)
375
+ personIds.push(String(r.from_uid));
376
+ const [i18nMap, personMap] = await Promise.all([
377
+ host.fetchI18nLabels(i18nIds),
378
+ host.resolveUsersByIds([...new Set(personIds)]),
379
+ ]);
380
+ const nameMap = {};
381
+ for (const [uid, p] of Object.entries(personMap))
382
+ nameMap[uid] = p.name;
383
+ const authUid = await resolveAuthUid(host);
384
+ const messages = msgRows.map((row) => {
385
+ const msgObj = parseMsg(row.msg);
386
+ const parsed = parseContent(msgObj, { i18nMap, nameMap });
387
+ const sender = senderName(msgObj || {}, row.from_uid, authUid, nameMap);
388
+ const statusLabel = dealMode
389
+ ? (Number(row.deal_status) === 1 ? '已处理' : '未处理')
390
+ : (row.read_status === true || row.read_status === 1 ? '已读' : '未读');
391
+ return {
392
+ msgid: str(row.ser_msgid),
393
+ sender,
394
+ time: formatFullTime(row.msgtime),
395
+ title: parsed.title,
396
+ content: escapePipe(collapseNewlines(parsed.content)),
397
+ typeName: parsed.typeName,
398
+ must: parsed.must,
399
+ statusLabel,
400
+ };
401
+ });
402
+ return ymbSuccess('yimiaoban.sysmsg.query', {
403
+ group,
404
+ type: hasType ? Number(args.type) : undefined,
405
+ dealMode,
406
+ total: messages.length,
407
+ messages,
408
+ start,
409
+ end,
410
+ hasMore: messages.length >= num,
411
+ });
412
+ },
413
+ };
414
+ function parseTs(value, isEnd) {
415
+ const s = String(value ?? '').trim();
416
+ if (/^\d{10}$/.test(s))
417
+ return Number(s);
418
+ const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/);
419
+ if (!m)
420
+ throw ymbValidationError('time_invalid', `无法解析时间参数: ${s}`);
421
+ const hh = m[4] !== undefined ? Number(m[4]) : (isEnd ? 23 : 0);
422
+ const mm = m[5] !== undefined ? Number(m[5]) : (isEnd ? 59 : 0);
423
+ const ss = m[6] !== undefined ? Number(m[6]) : (isEnd ? 59 : 0);
424
+ return Math.floor(new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), hh, mm, ss).getTime() / 1000);
425
+ }
426
+ function idRange(tmSec) {
427
+ if (!tmSec || tmSec <= 0)
428
+ return { min: '0', max: '0' };
429
+ const base = BigInt(Math.floor(tmSec)) * 1000000000n;
430
+ return { min: base.toString(), max: (base + 999999999n).toString() };
431
+ }
432
+ export const sessionOperations = [
433
+ sessionList,
434
+ sysmsgGroupSearch,
435
+ sysmsgTypeSync,
436
+ sysmsgQuery,
437
+ ];