weaver-work-cli 0.1.32 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +2 -2
  2. package/dist/chunks/{archive-UMGX72K6.js → archive-XBVH7JU7.js} +2 -2
  3. package/dist/chunks/{archive-oa-N2IYBLF3.js → archive-oa-MZM3EKFQ.js} +2 -2
  4. package/dist/chunks/{asset-GLEGKFG5.js → asset-4XO7UOUW.js} +2 -2
  5. package/dist/chunks/{attachment-content-AQNX5N3S.js → attachment-content-VXSWANAC.js} +3 -2
  6. package/dist/chunks/{blog-E44OTVDA.js → blog-PGZKOPSC.js} +2 -2
  7. package/dist/chunks/{caizhilian-DQUFZXZL.js → caizhilian-BH4ZVGUN.js} +2 -2
  8. package/dist/chunks/{calendar-JKPIRKFD.js → calendar-MXEMTIO4.js} +2 -2
  9. package/dist/chunks/{chunk-QWY6PGL6.js → chunk-2LV74IWP.js} +361 -56
  10. package/dist/chunks/chunk-3SO6QL6U.js +73 -0
  11. package/dist/chunks/chunk-5DQEEGCC.js +229 -0
  12. package/dist/chunks/{chunk-G3QJULO2.js → chunk-6LLJFKJE.js} +1 -1
  13. package/dist/chunks/{chunk-C6R3B2ML.js → chunk-BPE37FPB.js} +35 -7
  14. package/dist/chunks/chunk-BYATLLEF.js +68 -0
  15. package/dist/chunks/chunk-BYBSCN4X.js +932 -0
  16. package/dist/chunks/{chunk-CVZ2JOSW.js → chunk-F746NKNF.js} +1 -1
  17. package/dist/chunks/{chunk-GCG2NMWR.js → chunk-HXJQI5NM.js} +4 -2
  18. package/dist/chunks/{chunk-YRVDW4WN.js → chunk-I6QGIBDM.js} +2 -2
  19. package/dist/chunks/{chunk-H7J4DC5R.js → chunk-JCSN3JGM.js} +2 -2
  20. package/dist/chunks/{chunk-2GA5ZIQL.js → chunk-JNG42JKG.js} +30 -1
  21. package/dist/chunks/{chunk-DW6AUUKS.js → chunk-JUXVYKII.js} +9 -4
  22. package/dist/chunks/{chunk-6RKD4DRZ.js → chunk-JYKABYCV.js} +2 -2
  23. package/dist/chunks/{chunk-6EFWDL7Y.js → chunk-NCOVUAZK.js} +2 -2
  24. package/dist/chunks/{chunk-E4Z23GHM.js → chunk-QHFANTX7.js} +1114 -904
  25. package/dist/chunks/{ebuilder-form-V5FG2EHC.js → chunk-QYGO5EE2.js} +22 -1148
  26. package/dist/chunks/{chunk-IURUFGZK.js → chunk-T72N74ZT.js} +50 -33
  27. package/dist/chunks/{chunk-H6XSTSLM.js → chunk-TC6MZAO3.js} +137 -204
  28. package/dist/chunks/{chunk-WKI2FBIQ.js → chunk-VPUTVEMC.js} +737 -341
  29. package/dist/chunks/{chunk-WSKBSQ2U.js → chunk-VPXOPQJI.js} +2 -2
  30. package/dist/chunks/chunk-WXFPWCXG.js +774 -0
  31. package/dist/chunks/{chunk-4YC43QQ4.js → chunk-XMGSYD6S.js} +4 -4
  32. package/dist/chunks/conflict-JAB2LJEZ.js +21 -0
  33. package/dist/chunks/continuation-RN6PZN2M.js +11 -0
  34. package/dist/chunks/contract-XI2EEIZU.js +9110 -0
  35. package/dist/chunks/cowork-SCPGLBA4.js +2527 -0
  36. package/dist/chunks/ebuilder-form-EYLHNOTA.js +121 -0
  37. package/dist/chunks/{ehr-HMFY6VIL.js → ehr-EAP77IMT.js} +1344 -27
  38. package/dist/chunks/{esb-ZYSVB7SG.js → esb-2TGGXEE7.js} +4 -4
  39. package/dist/chunks/{fna-B7RKPAZD.js → fna-2X7ZE7BU.js} +13 -8
  40. package/dist/chunks/{host-OCH2BCCV.js → host-C6EL2RKL.js} +3 -3
  41. package/dist/chunks/{host-45WED2KK.js → host-I4NRK4MX.js} +3 -3
  42. package/dist/chunks/{host-I4Z6BFYX.js → host-Q4LZGCRU.js} +3 -3
  43. package/dist/chunks/host-UEI5FGDM.js +23 -0
  44. package/dist/chunks/{host-KNZ52LJZ.js → host-XCLDDXKQ.js} +3 -3
  45. package/dist/chunks/{hrm-2KAZGUHI.js → hrm-YXERJM76.js} +3 -3
  46. package/dist/chunks/{im-ACJJWH4U.js → im-A4VZN2TQ.js} +88 -12
  47. package/dist/chunks/{invoice-ZK3WCX2U.js → invoice-2Z6YPMIS.js} +3 -3
  48. package/dist/chunks/{jiuchuanhui-5PQD3IL2.js → jiuchuanhui-AR5GMKZB.js} +2 -2
  49. package/dist/chunks/{lazy-dispatch-JQXBW3GM.js → lazy-dispatch-QXW7P4X3.js} +4 -4
  50. package/dist/chunks/{mail-S2HDDXO2.js → mail-7GZ6CPBJ.js} +45 -24
  51. package/dist/chunks/{meeting-QVN3I37L.js → meeting-3NMGA3MB.js} +278 -43
  52. package/dist/chunks/{okr-QNG4VC2Q.js → okr-BZ6XUOAG.js} +2 -2
  53. package/dist/chunks/operations-5QL3AECO.js +11 -0
  54. package/dist/chunks/{plan-A25EMW6U.js → plan-DR5CMUYP.js} +2 -2
  55. package/dist/chunks/{project-G6XV66DZ.js → project-6J63MK33.js} +2 -2
  56. package/dist/chunks/{registry-DC6KR3NH.js → registry-KDD7DHNK.js} +4 -4
  57. package/dist/chunks/{registry-2VLUAESA.js → registry-OZSMDVPP.js} +2 -2
  58. package/dist/chunks/{registry-7C5UMIPL.js → registry-TKX2E7C7.js} +2 -2
  59. package/dist/chunks/{registry-TI3CIUXF.js → registry-VRTNC6UR.js} +8 -6
  60. package/dist/chunks/{room-recommend-WRTHO6Y2.js → room-recommend-XCWRMB5P.js} +4 -3
  61. package/dist/chunks/{schedule-KFIITGSK.js → schedule-IVBICP32.js} +4 -3
  62. package/dist/chunks/{workflow-DJ7SYWAF.js → workflow-D74NFK3C.js} +179 -6
  63. package/dist/index.js +35 -25
  64. package/dist/internal/e10/auth-recovery.js +14 -4
  65. package/dist/internal/e10/fetch-timing.js +31 -0
  66. package/dist/internal/e10/request-runtime.js +5 -0
  67. package/dist/shortcuts/contract/host.js +34 -59
  68. package/dist/shortcuts/cowork/continuation.js +70 -0
  69. package/dist/shortcuts/cowork/errors.js +59 -0
  70. package/dist/shortcuts/cowork/host.js +180 -0
  71. package/dist/shortcuts/cowork/index.js +166 -0
  72. package/dist/shortcuts/cowork/manifest.js +65 -0
  73. package/dist/shortcuts/cowork/operations/discuss-write.js +277 -0
  74. package/dist/shortcuts/cowork/operations/item-write.js +380 -0
  75. package/dist/shortcuts/cowork/operations/member-write.js +283 -0
  76. package/dist/shortcuts/cowork/operations/permission-write.js +286 -0
  77. package/dist/shortcuts/cowork/operations/read.js +423 -0
  78. package/dist/shortcuts/cowork/operations/registry.js +39 -0
  79. package/dist/shortcuts/cowork/operations/review-write.js +157 -0
  80. package/dist/shortcuts/cowork/operations/shared.js +215 -0
  81. package/dist/shortcuts/cowork/operations/types.js +39 -0
  82. package/dist/shortcuts/cowork/operations.js +19 -0
  83. package/dist/shortcuts/ehr/host.js +4 -1
  84. package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +757 -0
  85. package/dist/shortcuts/ehr/operations/recruit-position.js +367 -0
  86. package/dist/shortcuts/ehr/operations/recruit-stats.js +133 -0
  87. package/dist/shortcuts/ehr/operations/recruit-write.js +43 -16
  88. package/dist/shortcuts/ehr/operations/recruit.js +182 -10
  89. package/dist/shortcuts/ehr/operations/registry.js +6 -0
  90. package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
  91. package/dist/shortcuts/im/diagnostics.js +97 -0
  92. package/dist/shortcuts/im/host.js +58 -31
  93. package/dist/shortcuts/im/index.js +7 -2
  94. package/dist/shortcuts/im/operations/at-mention.js +127 -0
  95. package/dist/shortcuts/im/operations/file-user.js +10 -2
  96. package/dist/shortcuts/im/operations/group-read.js +26 -5
  97. package/dist/shortcuts/im/operations/group-write.js +35 -28
  98. package/dist/shortcuts/im/operations/msg-sync.js +7 -2
  99. package/dist/shortcuts/im/operations/msg-write.js +76 -10
  100. package/dist/shortcuts/im/operations/quote-mention.js +119 -0
  101. package/dist/shortcuts/im/operations/session.js +1 -1
  102. package/dist/shortcuts/im/operations/shared.js +10 -0
  103. package/dist/shortcuts/im/operations/types.js +13 -0
  104. package/dist/shortcuts/index.js +10 -0
  105. package/dist/shortcuts/mail/host.js +12 -1
  106. package/dist/shortcuts/mail/operations/types.js +17 -2
  107. package/dist/shortcuts/meeting/host.js +27 -34
  108. package/dist/shortcuts/meeting/index.js +79 -1
  109. package/dist/shortcuts/meeting/manifest.js +10 -0
  110. package/dist/shortcuts/meeting/operations/create.js +4 -1
  111. package/dist/shortcuts/meeting/operations/eb/catalog.js +10 -0
  112. package/dist/shortcuts/meeting/operations/eb/conflict.js +84 -8
  113. package/dist/shortcuts/meeting/operations/eb/create-flow.js +1195 -140
  114. package/dist/shortcuts/meeting/operations/eb/factory.js +170 -8
  115. package/dist/shortcuts/meeting/operations/eb/index.js +16 -3
  116. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +50 -5
  117. package/dist/shortcuts/meeting/operations/eb-fallback.js +163 -0
  118. package/dist/shortcuts/meeting/operations/env.js +99 -46
  119. package/dist/shortcuts/meeting/operations/form-fields.js +110 -0
  120. package/dist/shortcuts/meeting/operations/invitation.js +79 -20
  121. package/dist/shortcuts/meeting/operations/registry.js +2 -0
  122. package/dist/shortcuts/meeting/operations/standard-guard.js +71 -0
  123. package/dist/shortcuts/meeting/operations.js +16 -0
  124. package/dist/shortcuts/workflow/endpoints.js +8 -3
  125. package/dist/shortcuts/workflow/index.js +110 -1
  126. package/dist/shortcuts/workflow/operations/form.js +204 -36
  127. package/dist/shortcuts/workflow/operations/read.js +104 -23
  128. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +448 -0
  129. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +106 -0
  130. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +47 -0
  131. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +30 -23
  132. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
  133. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +1 -0
  134. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +2 -1
  135. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +2 -4
  136. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +138 -181
  137. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +12 -7
  138. package/dist/shortcuts/workflow/operations.js +23 -1
  139. package/docs/e10-auth.md +1 -1
  140. package/docs/ehr.md +1 -1
  141. package/docs/fna.md +2 -1
  142. package/docs/im.md +8 -1
  143. package/docs/todo-smart-convert/README.md +1 -1
  144. package/docs/todo-smart-convert/data-contract.md +2 -2
  145. package/docs/todo-smart-convert/subgroup-rules.md +6 -0
  146. package/package.json +1 -1
  147. package/skill-template/business-info.json +18 -0
  148. package/skill-template/domains/cowork.md +14 -0
  149. package/skill-template/domains/yimiaoban.md +3 -3
  150. package/skill-template/skill-template.md +1 -1
  151. package/skills/weaver-e10-blog/SKILL.md +5 -5
  152. package/skills/weaver-e10-caizhilian/SKILL.md +5 -5
  153. package/skills/weaver-e10-calendar/SKILL.md +5 -5
  154. package/skills/weaver-e10-cowork/SKILL.md +101 -0
  155. package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
  156. package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
  157. package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
  158. package/skills/weaver-e10-esb/SKILL.md +5 -5
  159. package/skills/weaver-e10-hrm/SKILL.md +5 -5
  160. package/skills/weaver-e10-jinchengda/SKILL.md +2 -2
  161. package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -5
  162. package/skills/weaver-e10-jucailin/SKILL.md +38 -14
  163. package/skills/weaver-e10-jucailin/references/recruit-create.md +104 -0
  164. package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
  165. package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
  166. package/skills/weaver-e10-jucailin/references/salary.md +15 -11
  167. package/skills/weaver-e10-mail/SKILL.md +17 -14
  168. package/skills/weaver-e10-meeting/SKILL.md +442 -46
  169. package/skills/weaver-e10-meeting/references/meeting-eb.md +135 -3
  170. package/skills/weaver-e10-meeting/references/meeting-invitation.md +28 -2
  171. package/skills/weaver-e10-meeting/references/meeting-queries.md +9 -6
  172. package/skills/weaver-e10-meeting/references/meeting-verification.md +8 -6
  173. package/skills/weaver-e10-meeting.zip +0 -0
  174. package/skills/weaver-e10-okr/SKILL.md +5 -5
  175. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  176. package/skills/weaver-e10-plan/SKILL.md +5 -5
  177. package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
  178. package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
  179. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
  180. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
  181. package/skills/weaver-e10-shared/SKILL.md +12 -12
  182. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -4
  183. package/skills/weaver-e10-wenshuding/SKILL.md +5 -5
  184. package/skills/weaver-e10-workflow/SKILL.md +29 -5
  185. package/skills/weaver-e10-workflow/aigroup/default.json +88 -0
  186. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +66 -14
  187. package/skills/weaver-e10-workflow/references/workflow-agent-entry.md +1 -1
  188. package/skills/weaver-e10-workflow/references/workflow-memory.md +1 -1
  189. package/skills/weaver-e10-workflow/references/workflow-query.md +12 -10
  190. package/skills/weaver-e10-workflow/references/workflow-view.md +2 -2
  191. package/skills/weaver-e10-workflow/tools/todo-serve.cjs +239 -0
  192. package/skills/weaver-e10-workflow/{scripts → tools}/todo-summary.cjs +218 -28
  193. package/skills/weaver-e10-yepiaotong/SKILL.md +10 -8
  194. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
  195. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +10 -10
  196. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +40 -37
  197. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -4
  198. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +27 -38
  199. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +4 -4
  200. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +37 -29
  201. package/skills/weaver-e10-yimiaoban/SKILL.md +156 -33
  202. package/skills/weaver-e10-yimiaoban/references/ding-write.md +1 -1
  203. package/skills/weaver-e10-yimiaoban/references/error-codes.md +5 -1
  204. package/skills/weaver-e10-yimiaoban/references/group-read.md +5 -1
  205. package/skills/weaver-e10-yimiaoban/references/group-write.md +6 -4
  206. package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -0
  207. package/skills/weaver-e10-yimiaoban/references/msg-write.md +34 -2
  208. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +5 -2
  209. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +79 -58
  210. package/skills/weaver-e10-yimiaoban/references/user-remind.md +2 -2
  211. package/skills/weaver-e10-ziguanjia/SKILL.md +5 -5
@@ -42,7 +42,7 @@ const LIST_KEYWORDS = {
42
42
  biz_config: ['业务配置映射表'],
43
43
  };
44
44
  /** 列表类型 -> 缓存键 + 表单逻辑名(candidate.list 四列表映射,同 recruit-list.mjs TYPES) */
45
- const LIST_TYPES = {
45
+ export const LIST_TYPES = {
46
46
  demand: { listKey: 'demand', formType: 'demand_manage', title: '招聘需求' },
47
47
  position: { listKey: 'position', formType: 'position_manage', title: '招聘职位' },
48
48
  entry: { listKey: 'entry', formType: 'entry_manage', title: '入职管理' },
@@ -58,7 +58,7 @@ const INTERNAL_KEYS = new Set([
58
58
  const DISPLAY_UI_KEYS = new Set([
59
59
  ...INTERNAL_KEYS, 'operate', 'isToDo', 'todoPropSpan', 'flow_status',
60
60
  ]);
61
- function randHex32() {
61
+ export function randHex32() {
62
62
  return randomUUID().replace(/-/g, '');
63
63
  }
64
64
  function randHex6() {
@@ -419,7 +419,7 @@ export function createRecruitContextLoader() {
419
419
  return { load };
420
420
  }
421
421
  /** EB 列表通用查询体(同 recruit-list.mjs baseBody) */
422
- function buildListBody(params) {
422
+ export function buildListBody(params) {
423
423
  return {
424
424
  current: params.current ?? 1,
425
425
  pageSize: params.pageSize ?? 10,
@@ -444,7 +444,7 @@ function buildListBody(params) {
444
444
  };
445
445
  }
446
446
  /** 列表 + 计数并行;displayData 优先(同 recruit-search/recruit-list 的显示层策略) */
447
- async function fetchListRows(host, objId, listId, body) {
447
+ export async function fetchListRows(host, objId, listId, body) {
448
448
  const countBody = { ...body };
449
449
  delete countBody.current;
450
450
  delete countBody.pageSize;
@@ -466,8 +466,33 @@ async function fetchListRows(host, objId, listId, body) {
466
466
  const hasDisplay = Array.isArray(displayRows) && displayRows.length > 0;
467
467
  const rows = hasDisplay ? displayRows : rawRows;
468
468
  const countData = countResponse ? normalizePaged(countResponse).data : null;
469
- const total = countData?.total !== undefined ? countData.total : rows.length;
470
- return { rows, displayRows: hasDisplay ? displayRows : null, total };
469
+ const pageSize = typeof body.pageSize === 'number'
470
+ ? body.pageSize
471
+ : (typeof body.pageSize === 'string' ? (Number(body.pageSize) || rows.length) : rows.length);
472
+ // 总数取值(不输出假值,对齐 ehr recruit-list.mjs 三态降级):
473
+ // ① 计数接口可用(200 且 total 为数字)→ 精确;
474
+ // ② 计数不可用但本页未满(rows < pageSize,即已到末页)→ 总数=已取回行数(精确);
475
+ // ③ 计数不可用且本页已满 → total=null(总数不可知),附 note 提示加大 pageSize。
476
+ // 背景:部分租户/列表 getListDataCount 恒 500,且 getListDatas 的 total 不可信,唯一可靠判据是分页本身。
477
+ const countUsable = countData !== null && typeof countData.total === 'number';
478
+ let total;
479
+ let totalExact;
480
+ let totalNote;
481
+ if (countUsable) {
482
+ total = countData.total;
483
+ totalExact = true;
484
+ }
485
+ else if (rows.length < pageSize) {
486
+ total = rows.length;
487
+ totalExact = true;
488
+ totalNote = '计数接口不可用,本页未满(已到末页),总数按已取回行数得出';
489
+ }
490
+ else {
491
+ total = null;
492
+ totalExact = false;
493
+ totalNote = '计数接口不可用且本页已满,总数不可知(可调大 pageSize 取回全部行数)';
494
+ }
495
+ return { rows, displayRows: hasDisplay ? displayRows : null, total, totalExact, totalNote };
471
496
  }
472
497
  // ─────────────────────────────────────────────────────────────────────────────
473
498
  // operations
@@ -537,7 +562,7 @@ const candidateSearch = {
537
562
  pageSize: toNumber(args.pageSize, 'pageSize') ?? 10,
538
563
  conditions,
539
564
  });
540
- const { rows, displayRows, total } = await fetchListRows(env.host, talentObjId, talentList.listId, body);
565
+ const { rows, displayRows, total, totalExact, totalNote } = await fetchListRows(env.host, talentObjId, talentList.listId, body);
541
566
  // 显示层优先:displayData 已内联人员/字典中文,零额外请求;
542
567
  // 缺失时回退原始层:字典映射 + 纯 ID 经 hrm getEmployeeByIds 批量反查(兜底,行为不变)
543
568
  let creatorNameMap = {};
@@ -590,7 +615,7 @@ const candidateSearch = {
590
615
  createTime: row?.create_time || null,
591
616
  };
592
617
  });
593
- return ehrSuccess('ehr.recruit.candidate.search', { total, rows: dataRows });
618
+ return ehrSuccess('ehr.recruit.candidate.search', { total, totalExact, ...(totalNote ? { totalNote } : {}), rows: dataRows });
594
619
  },
595
620
  };
596
621
  const candidateList = {
@@ -632,7 +657,7 @@ const candidateList = {
632
657
  current: toPositiveInt(args.current, 'current') ?? 1,
633
658
  pageSize: toNumber(args.pageSize, 'pageSize') ?? 10,
634
659
  });
635
- const { rows, displayRows, total } = await fetchListRows(env.host, formObjId, listParam.listId, body);
660
+ const { rows, displayRows, total, totalExact, totalNote } = await fetchListRows(env.host, formObjId, listParam.listId, body);
636
661
  const outRows = rows.map((row) => {
637
662
  const cleaned = { form_data_id: row?.form_data_id || row?.id };
638
663
  for (const [key, value] of Object.entries(row || {})) {
@@ -669,7 +694,153 @@ const candidateList = {
669
694
  return cleaned;
670
695
  });
671
696
  return ehrSuccess('ehr.recruit.candidate.list', {
672
- type, total, current: body.current, pageSize: body.pageSize, rows: outRows,
697
+ type, total, totalExact, ...(totalNote ? { totalNote } : {}), current: body.current, pageSize: body.pageSize, rows: outRows,
698
+ });
699
+ },
700
+ };
701
+ /** 当前登录人姓名(teamsCheck → currentUser.username),用于「我作为面试官」匹配 */
702
+ /** 人员字段可能是 JSON 数组串([{name,id,...}])→ 取姓名连接;纯文本原样返回 */
703
+ function personNames(value) {
704
+ const text = String(value ?? '').trim();
705
+ if (!text)
706
+ return '';
707
+ if (text.startsWith('[')) {
708
+ try {
709
+ const arr = JSON.parse(text);
710
+ if (Array.isArray(arr)) {
711
+ const names = arr
712
+ .map((item) => (typeof item === 'string' ? item : item?.name ?? item?.username ?? ''))
713
+ .filter(Boolean);
714
+ if (names.length)
715
+ return names.join('、');
716
+ }
717
+ }
718
+ catch { /* 非 JSON,按纯文本处理 */ }
719
+ }
720
+ return text;
721
+ }
722
+ /** 展示名兜底:跨基线 displayData 会把关联字段直接给名字(talent_id=人名、position_manage_id=职位名);纯 16 位以上数字视为 id,不当名字用 */
723
+ function nameLike(value) {
724
+ const text = String(value ?? '').trim();
725
+ if (!text || /^\d{16,}$/.test(text))
726
+ return null;
727
+ return text;
728
+ }
729
+ async function resolveCurrentUserName(host) {
730
+ // teamsCheck 的响应不被 isBusinessSuccess 认可(无 status 字段),故直接 fetch,绕开业务判定
731
+ try {
732
+ const session = await host.getSession();
733
+ const baseUrl = String(session?.baseUrl || '');
734
+ if (!baseUrl)
735
+ return '';
736
+ const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
737
+ method: 'POST',
738
+ headers: {
739
+ accept: 'application/json',
740
+ 'content-type': 'application/json',
741
+ eteamsid: String(session?.eteamsId || ''),
742
+ cookie: typeof session?.cookieHeader === 'function' ? session.cookieHeader() : '',
743
+ 'user-agent': typeof session?.uaHeader === 'function' ? session.uaHeader() : 'weaver-work-cli',
744
+ origin: baseUrl,
745
+ },
746
+ redirect: 'manual',
747
+ });
748
+ const data = await resp.json().catch(() => ({}));
749
+ const cu = data.currentUser ?? data.data?.currentUser ?? null;
750
+ return cu ? String(cu.username ?? cu.id ?? '') : '';
751
+ }
752
+ catch {
753
+ return '';
754
+ }
755
+ }
756
+ const interviewFeedbackTodo = {
757
+ name: 'ehr.recruit.interview.feedbackTodo',
758
+ resource: 'recruit',
759
+ method: 'interview.feedbackTodo',
760
+ action: 'ehr.recruit.interview.feedbackTodo',
761
+ summary: '面试待办:**只返回我作为面试官的 AI 面试待反馈清单**(候选人已答完、已有录像、我还没填意见);按需翻页 + 客户端筛选,不返回全量面试',
762
+ capability: 'recruit',
763
+ risk: READ,
764
+ inputSchema: schemaOf({
765
+ maxPages: { type: 'integer', minimum: 1, maximum: 50, description: '最多翻页数(默认 5)' },
766
+ pageSize: { type: 'integer', minimum: 1, maximum: 200, description: '单页条数(默认 200,实测服务端上限 200)' },
767
+ }),
768
+ handler: async (args, env) => {
769
+ if (!env.recruitContext)
770
+ throw ehrValidationError('recruit_context_unavailable', '招聘上下文加载器未初始化');
771
+ const cache = await env.recruitContext.load(env.host);
772
+ const listParam = cache.lists.interview;
773
+ const form = cache.forms.interview_plan;
774
+ if (!listParam?.listId || !form?.objId) {
775
+ throw new RecruitContextError('recruit_list_unavailable', '当前环境未启用「面试管理」功能(未找到面试列表/表单参数,按能力探测拦截)。');
776
+ }
777
+ const pageSize = toNumber(args.pageSize, 'pageSize') ?? 200;
778
+ const maxPages = toNumber(args.maxPages, 'maxPages') ?? 5;
779
+ const me = await resolveCurrentUserName(env.host);
780
+ // 枚举可能回退成 optionId:用环境缓存的 field_options 运行时映射回中文(不写死 id)
781
+ const fields = cache.fields[form.objId] || {};
782
+ const fieldOpts = cache.field_options[form.objId] || {};
783
+ const statusOpts = fieldOpts[fields.union_status] || {};
784
+ const typeOpts = fieldOpts[fields.interview_type] || {};
785
+ const toCn = (map, raw) => {
786
+ const text = String(raw ?? '');
787
+ return map[text] ?? text;
788
+ };
789
+ const statusOf = (row) => toCn(statusOpts, row?.union_status_key ?? row?.union_status);
790
+ const typeOf = (row) => toCn(typeOpts, row?.interview_type_key ?? row?.interview_type);
791
+ const interviewerOf = (row) => personNames(row?.interviewer);
792
+ const scanned = [];
793
+ let lastPageCount = 0;
794
+ for (let i = 0; i < maxPages; i++) {
795
+ const body = buildListBody({
796
+ listId: listParam.listId,
797
+ cusMenuId: listParam.cusMenuId,
798
+ menuName: listParam.menuName || '面试管理',
799
+ title: '面试管理',
800
+ current: i + 1,
801
+ pageSize,
802
+ });
803
+ const page = await fetchListRows(env.host, form.objId, listParam.listId, body);
804
+ const use = page.displayRows && page.displayRows.length ? page.displayRows : page.rows;
805
+ scanned.push(...use);
806
+ lastPageCount = use.length;
807
+ if (use.length < pageSize)
808
+ break;
809
+ }
810
+ // 「待反馈」= 后端 InterviewStatusEnum.TO_FEEDBACK:
811
+ // AI 面试结束时间已过 且(有录像附件 或 已开始录制)且 反馈结果为空
812
+ // ⇒ 这个状态本身就等于「已完成 + 有视频 + 未填意见」,无需再读视频字段
813
+ const pending = scanned.filter((row) => /待反馈/.test(statusOf(row)));
814
+ const mineAll = me ? pending.filter((row) => interviewerOf(row).includes(me)) : [];
815
+ const mineAi = mineAll.filter((row) => /AI\s*面试/i.test(typeOf(row)));
816
+ const slim = (row) => ({
817
+ form_data_id: row?.form_data_id ?? row?.id ?? null,
818
+ candidate_name: nameLike(row?.candidate_name) ?? nameLike(row?.talent_id) ?? nameLike(row?.name),
819
+ position_name: nameLike(row?.position_name) ?? nameLike(row?.position_manage_id),
820
+ start_time: row?.start_time ?? null,
821
+ interview_type_key: typeOf(row) || null,
822
+ interview_session: row?.interview_session ?? null,
823
+ interviewer: interviewerOf(row) || null,
824
+ union_status_key: statusOf(row) || null,
825
+ subject: row?.subject ?? null,
826
+ talent_id: row?.talent_id ?? null,
827
+ position_manage_id: row?.position_manage_id ?? null,
828
+ });
829
+ const notes = [];
830
+ if (!me)
831
+ notes.push('未能解析当前登录人姓名(teamsCheck 失败),无法按面试官过滤');
832
+ if (lastPageCount >= pageSize && scanned.length >= pageSize * maxPages) {
833
+ notes.push(`已扫描 ${scanned.length} 条(达 maxPages=${maxPages} 上限),如怀疑遗漏可加大 maxPages`);
834
+ }
835
+ return ehrSuccess('ehr.recruit.interview.feedbackTodo', {
836
+ currentUser: me || null,
837
+ scanned: scanned.length,
838
+ myAiPendingFeedback: { count: mineAi.length, rows: mineAi.map(slim) },
839
+ myOtherPendingFeedback: {
840
+ count: mineAll.length - mineAi.length,
841
+ rows: mineAll.filter((row) => !/AI\s*面试/i.test(typeOf(row))).map(slim),
842
+ },
843
+ ...(notes.length ? { note: notes.join(';') } : {}),
673
844
  });
674
845
  },
675
846
  };
@@ -790,6 +961,7 @@ export const recruitOperations = [
790
961
  candidateSearch,
791
962
  candidateList,
792
963
  candidateCard,
964
+ interviewFeedbackTodo,
793
965
  ];
794
966
  export const RECRUIT_SECOND_PHASE_WITHHELD = [
795
967
  'recruit.candidate.create',
@@ -6,6 +6,9 @@ import { hrDataSourceOperations } from './hr-ds.js';
6
6
  import { hrReportOperations } from './hr-report.js';
7
7
  import { recruitOperations } from './recruit.js';
8
8
  import { recruitWriteOperations } from './recruit-write.js';
9
+ import { recruitStatsOperations } from './recruit-stats.js';
10
+ import { recruitPositionOperations } from './recruit-position.js';
11
+ import { recruitCandidateImportOperations } from './recruit-candidate-import.js';
9
12
  import { salaryOperations } from './salary.js';
10
13
  function buildRegistry() {
11
14
  const registry = new Map();
@@ -18,6 +21,9 @@ function buildRegistry() {
18
21
  ...hrReportOperations,
19
22
  ...recruitOperations,
20
23
  ...recruitWriteOperations,
24
+ ...recruitStatsOperations,
25
+ ...recruitPositionOperations,
26
+ ...recruitCandidateImportOperations,
21
27
  ];
22
28
  for (const definition of definitions) {
23
29
  if (registry.has(definition.name)) {
@@ -8,6 +8,8 @@ const MAX_ROWS_LIMIT = 5000;
8
8
  const USER_INPUT_LIMIT = 500;
9
9
  const BLOCKED_MESSAGE = /拦截|高危|安全校验/;
10
10
  const CLARIFY_MESSAGE = /不明确|请补充|补充查询/;
11
+ /** 权限类文案:命中即视为数据权限不足,Agent 必须停在原地,不得改调其它接口拼数据。 */
12
+ const PERMISSION_DENIED_MESSAGE = /权限|越权/;
11
13
  function requireUserInput(value) {
12
14
  if (typeof value !== 'string' || value.trim().length === 0) {
13
15
  throw fnaValidationError('user_input_required', 'userInput 必填(自然语言问题,不能为空白)');
@@ -65,16 +67,23 @@ function collectColumns(rows) {
65
67
  }
66
68
  return columns;
67
69
  }
70
+ /**
71
+ * 按文案推断字符串 data 的语义。
72
+ * 权限类文案优先归类为 clarify:服务端可能只返回「无数据权限」而不带「不明确」,
73
+ * 若落进 unexpected,Agent 会误判为无非预期形态,进而换问法或改调其它接口拼数据。
74
+ */
68
75
  function classifyMessage(message) {
76
+ if (PERMISSION_DENIED_MESSAGE.test(message))
77
+ return { kind: 'clarify', permissionDenied: true };
69
78
  if (BLOCKED_MESSAGE.test(message))
70
- return 'blocked';
79
+ return { kind: 'blocked', permissionDenied: false };
71
80
  if (CLARIFY_MESSAGE.test(message))
72
- return 'clarify';
81
+ return { kind: 'clarify', permissionDenied: false };
73
82
  return undefined;
74
83
  }
75
84
  /**
76
85
  * 归一化 getExpenseChart 的 data。code 恒为 200,成败只看 data 形态:
77
- * 对象数组 → rows/empty;追问/安全拦截文案 → clarify/blocked(按文案推断);其余 → unexpected。
86
+ * 对象数组 → rows/empty;追问/安全拦截/无权限文案 → clarify/blocked(按文案推断,无权限额外标 permissionDenied);其余 → unexpected。
78
87
  * maxRows 只截断本地返回,列名仍按服务端全量结果收集。
79
88
  */
80
89
  function normalizeChart(data, maxRows) {
@@ -107,9 +116,12 @@ function normalizeChart(data, maxRows) {
107
116
  });
108
117
  }
109
118
  if (typeof data === 'string') {
110
- const kind = classifyMessage(data);
111
- if (kind) {
112
- return fnaSuccess('fna.expense.chart', { kind, message: data }, { messageKindInferred: true });
119
+ const classified = classifyMessage(data);
120
+ if (classified) {
121
+ return fnaSuccess('fna.expense.chart', { kind: classified.kind, message: data }, {
122
+ messageKindInferred: true,
123
+ permissionDenied: classified.permissionDenied,
124
+ });
113
125
  }
114
126
  }
115
127
  return fnaSuccess('fna.expense.chart', { kind: 'unexpected' }, { dataType: dataTypeOf(data) });
@@ -124,7 +136,7 @@ const fnaExpenseChart = {
124
136
  resource: 'expense',
125
137
  method: 'expense.chart',
126
138
  action: 'fna.expense.chart',
127
- summary: '报销报表 AI 问答:用自然语言查询分组/排名/趋势,返回 kind=rows/empty/clarify/blocked/unexpected;maxRows 只截断本地展示',
139
+ summary: '报销报表 AI 问答:用自然语言查询分组/排名/趋势,返回 kind=rows/empty/clarify/blocked/unexpected;无权限文案额外返回 meta.permissionDenied=true,此时必须停住、不得改调其它接口拼数据;maxRows 只截断本地展示',
128
140
  capability: 'expense-chart',
129
141
  risk: READ,
130
142
  inputSchema: schemaOf({
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 易秒办(im)域内的诊断能力:**只动本模块目录,不改公共 core/cmd/internal**。
3
+ *
4
+ * 两个能力都通过公共已有的扩展点实现:
5
+ * - 失败信封双写:`im` 自己的 catch 分支里写 stdout + stderr,不扩 `Output` 类;
6
+ * - 请求级追踪:走 `HookManager.registerE10RequestInterceptor()`,不碰 `E10RequestRuntime`。
7
+ */
8
+ const TRACE_ENV = 'WEAVER_WORK_CLI_TRACE';
9
+ /** 同一进程内只注册一次拦截器(`forCommand()` 复用同一个 HookManager 实例)。 */
10
+ const registeredHooks = new WeakSet();
11
+ function truthy(value) {
12
+ const raw = String(value ?? '').toLowerCase();
13
+ return raw === '1' || raw === 'true' || raw === 'yes';
14
+ }
15
+ /** 从当前命令向上找 `im --trace`:命令级开关,不占用全局选项槽位。 */
16
+ function traceFromCommand(ctx) {
17
+ let current = ctx.activeCommand;
18
+ for (let depth = 0; current && depth <= 3; depth += 1) {
19
+ const opts = typeof current.opts === 'function' ? current.opts() : undefined;
20
+ if (opts?.trace === true)
21
+ return true;
22
+ current = current.parent ?? undefined;
23
+ }
24
+ return false;
25
+ }
26
+ export function imTraceEnabled(ctx) {
27
+ if (truthy(ctx.env?.[TRACE_ENV]) || truthy(process.env[TRACE_ENV]))
28
+ return true;
29
+ try {
30
+ return traceFromCommand(ctx);
31
+ }
32
+ catch {
33
+ // 单测里的 ctx 可能是最小 mock:追踪开关不得影响请求本身
34
+ return false;
35
+ }
36
+ }
37
+ /** 按 path 记录在途请求的起始时间;同 path 并发时按 FIFO 配对。 */
38
+ const inflight = new Map();
39
+ function remember(path) {
40
+ const list = inflight.get(path) ?? [];
41
+ list.push(Date.now());
42
+ inflight.set(path, list);
43
+ }
44
+ function forget(path) {
45
+ const list = inflight.get(path);
46
+ if (!list?.length)
47
+ return undefined;
48
+ const startedAt = list.shift();
49
+ if (!list.length)
50
+ inflight.delete(path);
51
+ return startedAt;
52
+ }
53
+ function label(request) {
54
+ const operation = request.operation || '-';
55
+ return `${request.method} ${request.path} op=${operation}`;
56
+ }
57
+ function traceInterceptor() {
58
+ return {
59
+ beforeRequest({ request }) {
60
+ remember(request.path);
61
+ },
62
+ afterResponse({ request, response }) {
63
+ const startedAt = forget(request.path);
64
+ const ms = startedAt === undefined ? '-' : `${Date.now() - startedAt}ms`;
65
+ process.stderr.write(`[trace] ${label(request)} status=${response.status} ${ms}\n`);
66
+ },
67
+ onError({ request, error }) {
68
+ const startedAt = forget(request.path);
69
+ const ms = startedAt === undefined ? '-' : `${Date.now() - startedAt}ms`;
70
+ const code = error?.subtype
71
+ || error?.code
72
+ || 'unknown';
73
+ // 只打方法/路径/operation/错误码/耗时,绝不打印请求头与请求体(可能含凭证)
74
+ process.stderr.write(`[trace] ${label(request)} status=ERR code=${code} ${ms}\n`);
75
+ },
76
+ };
77
+ }
78
+ /** 按需注册追踪拦截器:未开启 trace 时完全零开销。 */
79
+ export function registerImTrace(ctx) {
80
+ if (!imTraceEnabled(ctx))
81
+ return;
82
+ const hooks = ctx.hooks;
83
+ if (!hooks || registeredHooks.has(hooks))
84
+ return;
85
+ registeredHooks.add(hooks);
86
+ ctx.hooks.registerE10RequestInterceptor(traceInterceptor());
87
+ }
88
+ /**
89
+ * 失败信封:**stdout 与 stderr 各写一份**(内容相同)。
90
+ * stdout 是主通道 —— 只读 stdout 的调用方也能拿到结构化错误;
91
+ * stderr 保留一份,兼容「stderr 有内容 = 失败」的历史判断。
92
+ */
93
+ export function printImFailure(envelope) {
94
+ const text = `${JSON.stringify(envelope, null, 2)}\n`;
95
+ process.stdout.write(text);
96
+ process.stderr.write(text);
97
+ }
@@ -94,6 +94,7 @@ export function createImHost(ctx, dependencies = {}) {
94
94
  return createE10RequestRuntime(ctx, await ensureSession(), {
95
95
  interceptors: dependencies.interceptors,
96
96
  fetch: dependencies.fetch,
97
+ recoverAuthentication: dependencies.recoverAuthentication,
97
98
  });
98
99
  }
99
100
  async function request(request) {
@@ -144,41 +145,53 @@ export function createImHost(ctx, dependencies = {}) {
144
145
  }
145
146
  async function requestBinary(url, headers = {}) {
146
147
  const session = await ensureSession();
147
- const controller = new AbortController();
148
- const timer = setTimeout(() => controller.abort(), 60_000);
148
+ const baseUrl = new URL(session.getBaseUrl());
149
+ let target;
149
150
  try {
150
- const resp = await (dependencies.fetch || globalThis.fetch)(url, {
151
- headers: {
152
- accept: 'application/json, text/plain, */*',
153
- cookie: session.cookieHeader(),
154
- eteamsid: session.eteamsId,
155
- 'user-agent': session.uaHeader(),
156
- origin: session.getBaseUrl(),
157
- ...headers,
158
- },
159
- redirect: 'manual',
160
- signal: controller.signal,
151
+ target = new URL(url, baseUrl);
152
+ }
153
+ catch {
154
+ throw imValidationError('download_url_invalid', '下载地址不是合法 URL');
155
+ }
156
+ if (target.origin !== baseUrl.origin) {
157
+ throw imValidationError('download_url_cross_origin', '下载地址不允许跨 E10 环境');
158
+ }
159
+ assertPath(target.pathname);
160
+ const query = {};
161
+ for (const [key, value] of target.searchParams) {
162
+ const previous = query[key];
163
+ if (previous === undefined)
164
+ query[key] = value;
165
+ else if (Array.isArray(previous))
166
+ previous.push(value);
167
+ else
168
+ query[key] = [previous, value];
169
+ }
170
+ let response;
171
+ try {
172
+ response = await (await runtime()).requestBinary({
173
+ module: 'im',
174
+ operation: 'im.file.download',
175
+ method: 'GET',
176
+ path: target.pathname,
177
+ query,
178
+ headers,
179
+ timeoutMs: 60_000,
161
180
  });
162
- if (resp.status === 401 || resp.status === 302) {
163
- throw imAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE);
164
- }
165
- if (resp.status !== 200) {
166
- throw new ImCliError('api', 'download_failed', `下载失败 HTTP ${resp.status}`, {
167
- code: resp.status,
168
- exitCode: 1,
169
- });
170
- }
171
- return Buffer.from(await resp.arrayBuffer());
172
181
  }
173
182
  catch (error) {
174
- if (error instanceof ImCliError)
175
- throw error;
176
- const message = error instanceof Error ? error.message : String(error);
177
- throw imNetworkError('transport', `下载失败:${message}`, { retryable: true });
183
+ mapRuntimeNetworkError(error);
184
+ }
185
+ if (response.status === 401 || response.status === 302) {
186
+ throw imAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE);
178
187
  }
179
- finally {
180
- clearTimeout(timer);
188
+ if (response.status !== 200) {
189
+ throw new ImCliError('api', 'download_failed', `下载失败 HTTP ${response.status}`, {
190
+ code: response.status,
191
+ exitCode: 1,
192
+ });
181
193
  }
194
+ return response.content;
182
195
  }
183
196
  /** teamsCheck:解析当前登录人的 imUid/imCid/name(IM uid 语义 = 员工 id)。 */
184
197
  async function getSelfIdentity() {
@@ -248,11 +261,25 @@ export function createImHost(ctx, dependencies = {}) {
248
261
  timeoutMs: 15_000,
249
262
  });
250
263
  const preData = preRes?.data || {};
264
+ /**
265
+ * 秒传分支:服务端按 MD5 命中已有文件时**不再返回 preUploadId**,而是直接给出 fileObj
266
+ * (`uploadType: "second"`)。实测两种结构:
267
+ * { uploadType: 'second', fileObj: {...} }
268
+ * { uploadType: 'second', imUploadInfoDto: { uploadType: 'second', fileObj: {...}, transcode: {...} } }
269
+ * 旧实现只认 preUploadId,导致「同一张图/同一个文件第二次发送」必定失败(upload_check_failed,
270
+ * 但响应本身是 code:200 status:true)。命中秒传时直接复用 fileObj,跳过 valid/upload。
271
+ */
272
+ const preInfo = preData.imUploadInfoDto || preData;
273
+ const preFileObj = preInfo?.fileObj && (preInfo.fileObj.id || preInfo.fileObj.fileid) ? preInfo.fileObj : null;
274
+ const preUploadType = String(preData.uploadType || preInfo?.uploadType || '');
275
+ if (preFileObj && (!preData.preUploadId || preUploadType === 'second')) {
276
+ return preFileObj;
277
+ }
251
278
  if (!preData.preUploadId) {
252
- throw new ImCliError('api', 'upload_check_failed', `preUploadCheck 失败`, { details: preRes, exitCode: 1 });
279
+ throw new ImCliError('api', 'upload_check_failed', `preUploadCheck 失败(未返回 preUploadId,也没有可复用的秒传 fileObj)`, { details: preRes, exitCode: 1 });
253
280
  }
254
281
  const preUploadId = String(preData.preUploadId);
255
- const uploadType = preData.uploadType || 'common';
282
+ const uploadType = preUploadType || 'common';
256
283
  // 第 2 步:实传
257
284
  const upForm = new FormData();
258
285
  upForm.append('data', new Blob([data], { type: mime }), name);
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { ExitCode, isCliError, toErrorEnvelope } from '../../core/errors.js';
3
3
  import { createImContinuationSigner } from './continuation.js';
4
+ import { printImFailure, registerImTrace } from './diagnostics.js';
4
5
  import { imValidationError } from './errors.js';
5
6
  import { createImHost } from './host.js';
6
7
  import { imManifest } from './manifest.js';
@@ -81,6 +82,8 @@ function failureEnvelope(error) {
81
82
  };
82
83
  }
83
84
  async function executeAndPrint(ctx, operation, input) {
85
+ // 排障/接口计数:`im --trace` 或 WEAVER_WORK_CLI_TRACE=1(实现完全在 im 域内,不改公共 runtime)
86
+ registerImTrace(ctx);
84
87
  try {
85
88
  const result = await executeImOperation(operation, input, {
86
89
  ctx,
@@ -90,7 +93,7 @@ async function executeAndPrint(ctx, operation, input) {
90
93
  ctx.output.write(successEnvelope(operation, result), { json: true });
91
94
  }
92
95
  catch (error) {
93
- console.error(JSON.stringify(failureEnvelope(error), null, 2));
96
+ printImFailure(failureEnvelope(error));
94
97
  process.exitCode = isCliError(error) ? error.exitCode : ExitCode.GeneralError;
95
98
  }
96
99
  }
@@ -110,7 +113,9 @@ export const imShortcut = {
110
113
  description: '泛微E10易秒办(即时通讯)CLI',
111
114
  category: 'shortcut',
112
115
  register({ program, action }) {
113
- const im = program.command('im').description('泛微E10易秒办:消息/群组/会话/系统消息');
116
+ const im = program.command('im')
117
+ .description('泛微E10易秒办:消息/群组/会话/系统消息')
118
+ .option('--trace', '每个 E10 请求在 stderr 打一行 [trace](method/path/op/status/耗时,不含凭证)');
114
119
  im.command('schema')
115
120
  .description('Print im operation manifest for agents')
116
121
  .action(action((ctx) => {