weaver-work-cli 0.1.39 → 0.1.40

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 (103) hide show
  1. package/README.md +4 -2
  2. package/dist/chunks/{archive-ENZYBBME.js → archive-JHTONRST.js} +2 -2
  3. package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-FCHLCPHC.js} +2 -2
  4. package/dist/chunks/{asset-OOV6DWPA.js → asset-RLIHC7DB.js} +2 -2
  5. package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-KVOFTLCP.js} +2 -2
  6. package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-RZYDB3MB.js} +3 -3
  7. package/dist/chunks/{blog-PEPOJAL7.js → blog-42OZTJNP.js} +2 -2
  8. package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-KRK547K5.js} +2 -2
  9. package/dist/chunks/{calendar-CIARBAOX.js → calendar-RTWAMKHD.js} +2 -2
  10. package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-4CTESSKI.js} +4 -4
  11. package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-4TEDNCON.js} +43 -5
  12. package/dist/chunks/chunk-57IECMWX.js +487 -0
  13. package/dist/chunks/{chunk-CQHGNCK2.js → chunk-7BRU4EFH.js} +13 -7
  14. package/dist/chunks/{chunk-KTCB5R5H.js → chunk-7E6MUOEK.js} +2 -2
  15. package/dist/chunks/{chunk-I2HB737U.js → chunk-7U577Q7U.js} +2 -2
  16. package/dist/chunks/{chunk-BJGXMFGD.js → chunk-CAQNBBQA.js} +2 -2
  17. package/dist/chunks/{chunk-MUQUHKPG.js → chunk-CXRQWOZ5.js} +2 -2
  18. package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-FLS4DPGP.js} +110 -16
  19. package/dist/chunks/{chunk-P2DAISYW.js → chunk-I566ZYNF.js} +1 -1
  20. package/dist/chunks/{chunk-36DIFZAH.js → chunk-IZSSVYAG.js} +2 -2
  21. package/dist/chunks/{chunk-EINRJHQ3.js → chunk-JWRZM2H2.js} +3 -3
  22. package/dist/chunks/{chunk-F2EQXUEE.js → chunk-LFMSDBZN.js} +97 -14
  23. package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-LN447PY2.js} +91 -50
  24. package/dist/chunks/{chunk-K3RUEEOL.js → chunk-NETCCARD.js} +2 -2
  25. package/dist/chunks/{chunk-YS5DPN4T.js → chunk-NJKREDA3.js} +1 -1
  26. package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-SPRHHSVC.js} +2 -2
  27. package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-T6EGVBKU.js} +1 -1
  28. package/dist/chunks/{chunk-NLK2RZKG.js → chunk-VNBU55SL.js} +428 -145
  29. package/dist/chunks/{chunk-EZGFHJFW.js → chunk-WFFNM65B.js} +26 -7
  30. package/dist/chunks/{conflict-DRMDOL56.js → conflict-VR55AOOO.js} +2 -2
  31. package/dist/chunks/{contract-73MERZWN.js → contract-HI4FNPYE.js} +2 -2
  32. package/dist/chunks/{cowork-HNGBPD7A.js → cowork-HJLI353D.js} +12 -5
  33. package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-HTGI63UU.js} +3 -3
  34. package/dist/chunks/{ehr-RAQNEQO7.js → ehr-GYXIYH3A.js} +2 -2
  35. package/dist/chunks/env-DW3G4HUO.js +19 -0
  36. package/dist/chunks/{esb-6XV7C7YV.js → esb-I5INCEFE.js} +4 -4
  37. package/dist/chunks/factory-TKXGXS6L.js +30 -0
  38. package/dist/chunks/{fna-XTSRB7G7.js → fna-K2MHTQ24.js} +2 -2
  39. package/dist/chunks/group-chat-VYBNMJLG.js +18 -0
  40. package/dist/chunks/{host-VGPVP7YF.js → host-256GVCAH.js} +3 -3
  41. package/dist/chunks/{host-A7J6GDUP.js → host-D5SERMAJ.js} +3 -3
  42. package/dist/chunks/{host-3F5UM5GN.js → host-JM4YVHFM.js} +3 -3
  43. package/dist/chunks/{host-YAQAPYNP.js → host-L3VLITMT.js} +3 -3
  44. package/dist/chunks/{host-YMBXWFWX.js → host-ZUQTJYYK.js} +3 -3
  45. package/dist/chunks/{hrm-TLHKC545.js → hrm-VLRBKTZB.js} +3 -3
  46. package/dist/chunks/{im-YOUCFMUK.js → im-QK3VTLXK.js} +8 -8
  47. package/dist/chunks/{invoice-WNBS2B4C.js → invoice-T5JTA32H.js} +3 -3
  48. package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-P2FNEQVZ.js} +2 -2
  49. package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-XRRI46M6.js} +4 -4
  50. package/dist/chunks/{mail-LRYMBFNL.js → mail-3Y7MJ6PS.js} +9 -4
  51. package/dist/chunks/{meeting-ESXL6MFI.js → meeting-M7LTXGJE.js} +11 -10
  52. package/dist/chunks/{okr-EOV65SSF.js → okr-OBC6S67L.js} +2 -2
  53. package/dist/chunks/{plan-YFPVKW6Y.js → plan-5H6I53SK.js} +2 -2
  54. package/dist/chunks/{project-MDVZUYQW.js → project-L75QZV4S.js} +2 -2
  55. package/dist/chunks/{registry-QCLZEDAP.js → registry-KMHCPAAD.js} +9 -8
  56. package/dist/chunks/{registry-OZSMDVPP.js → registry-QGHFNUWS.js} +2 -2
  57. package/dist/chunks/{registry-VT5Y2HLA.js → registry-RXPN6BBT.js} +4 -4
  58. package/dist/chunks/{registry-YX4CSEMD.js → registry-UGRBCBHO.js} +2 -2
  59. package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-A3UDC3CA.js} +4 -4
  60. package/dist/chunks/{schedule-UASAVDVD.js → schedule-5GCACCK7.js} +4 -4
  61. package/dist/chunks/{workflow-KDGIUCNW.js → workflow-M5ZJ7DWF.js} +6 -6
  62. package/dist/index.js +28 -28
  63. package/dist/internal/e10/auth/commands.js +109 -56
  64. package/dist/internal/e10/auth/session.js +11 -1
  65. package/dist/internal/e10/auth/xiaoe.js +29 -4
  66. package/dist/internal/e10/client.js +13 -0
  67. package/dist/internal/e10/context.js +14 -6
  68. package/dist/internal/e10/request-runtime.js +9 -0
  69. package/dist/shortcuts/cowork/operations/item-write.js +10 -2
  70. package/dist/shortcuts/mail/operations/mail-send.js +8 -2
  71. package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -1
  72. package/dist/shortcuts/meeting/operations/eb/create-flow.js +381 -104
  73. package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
  74. package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
  75. package/dist/shortcuts/meeting/operations/eb/group-chat.js +583 -0
  76. package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
  77. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +19 -3
  78. package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
  79. package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
  80. package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
  81. package/dist/shortcuts/workflow/operations/form.js +21 -2
  82. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
  83. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +10 -9
  84. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +28 -2
  85. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
  86. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +17 -9
  87. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +1 -0
  88. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +14 -2
  89. package/docs/e10-auth.md +15 -0
  90. package/package.json +1 -1
  91. package/scripts/mcp-meeting.mjs +234 -0
  92. package/skills/weaver-e10-cowork/references/cowork-write.md +1 -1
  93. package/skills/weaver-e10-mail/references/mail-send.md +2 -0
  94. package/skills/weaver-e10-meeting/SKILL.md +88 -25
  95. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
  96. package/skills/weaver-e10-meeting.zip +0 -0
  97. package/skills/weaver-e10-shared/SKILL.md +1 -1
  98. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +1 -1
  99. package/skills/weaver-e10-workflow/SKILL.md +4 -0
  100. package/skills/weaver-e10-workflow/aigroup/default.json +12 -0
  101. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +17 -11
  102. package/skills/weaver-e10-workflow/tools/todo-summary.cjs +73 -23
  103. package/dist/chunks/{chunk-2LV74IWP.js → chunk-E7OK5WD5.js} +5 -5
@@ -0,0 +1,99 @@
1
+ import { EB_BASE, ebBusinessSuccess, extractEbRows, queryBody } from './factory.js';
2
+ import { readCache, writeCache, cacheScopeSuffix } from './cache.js';
3
+ /** 占用缓存 TTL:太长会放行别人刚订的房间,太短失去缓存意义 —— 5 分钟 + 提交即作废。 */
4
+ const USAGE_TTL_MS = 5 * 60 * 1000;
5
+ const MAX_PAGES = 10;
6
+ const PAGE_SIZE = 200;
7
+ /** 取 'yyyy-MM-dd HH:mm' 的日期部分。 */
8
+ export function dayOf(datetime) {
9
+ return String(datetime ?? '').trim().slice(0, 10);
10
+ }
11
+ /**
12
+ * 时间重叠判定:占用开始 < 我的结束 且 占用结束 > 我的开始。
13
+ * 字符串直接比较成立的前提是 'yyyy-MM-dd HH:mm' 定宽格式(EB 出参即如此)。
14
+ */
15
+ export function usageOverlaps(row, begin, end) {
16
+ return row.start < end && row.end > begin;
17
+ }
18
+ /** 从全天占用里筛出「某会议室在某时段」的占用记录(可排除自身会议)。 */
19
+ export function filterUsages(rows, options) {
20
+ return rows.filter((row) => {
21
+ if (options.roomId && row.roomId !== options.roomId)
22
+ return false;
23
+ if (options.excludeMtId && row.mtId && row.mtId === options.excludeMtId)
24
+ return false;
25
+ return usageOverlaps(row, options.begin, options.end);
26
+ });
27
+ }
28
+ /** 从全天占用算出「某时段被占用的会议室 id 集合」(替代按时段的 query_roomusage_list)。 */
29
+ export function busyRoomIds(rows, begin, end) {
30
+ const ids = new Set();
31
+ for (const row of rows) {
32
+ if (row.roomId && usageOverlaps(row, begin, end))
33
+ ids.add(row.roomId);
34
+ }
35
+ return ids;
36
+ }
37
+ function usageKey(scope, day) {
38
+ return `usage-v1-${scope}-${day}`;
39
+ }
40
+ /**
41
+ * 加载某天的全部会议室占用(缓存优先)。
42
+ * 返回 rows 供本地过滤;fromCache=true 表示没打网络(调用方可用于提示/计时)。
43
+ */
44
+ export async function loadDayUsages(env, day, options = {}) {
45
+ const scope = await cacheScopeSuffix(env);
46
+ const key = usageKey(scope, day);
47
+ if (options.refresh !== true) {
48
+ const cached = await readCache(env, key, { ttlMs: USAGE_TTL_MS });
49
+ if (cached) {
50
+ return { rows: cached.data, fromCache: true, ageMs: cached.ageMs, requests: 0 };
51
+ }
52
+ }
53
+ const session = await env.host.getSession();
54
+ const rows = [];
55
+ let requests = 0;
56
+ for (let pageNo = 1; pageNo <= MAX_PAGES; pageNo += 1) {
57
+ const response = await env.host.request({
58
+ method: 'POST',
59
+ path: `${EB_BASE}/getFormDataList/query_roomusage_list`,
60
+ operation: 'meeting.eb.roomUsage.list',
61
+ // 与 fetchBusyRoomIds 同一套算子语义:enddatetime lt 当天末尾、begindatetime gt 当天开头
62
+ query: { enddatetime: `${day} 23:59`, begindatetime: `${day} 00:00` },
63
+ body: queryBody(session.userId, { pageNo, pageSize: PAGE_SIZE }),
64
+ acceptBusiness: ebBusinessSuccess,
65
+ });
66
+ requests += 1;
67
+ const { rows: pageRows } = extractEbRows(response);
68
+ for (const row of pageRows) {
69
+ const main = row?.mainTable ?? row ?? {};
70
+ const roomId = String(main.address ?? main.room_id ?? '').trim();
71
+ const start = String(main.startdate ?? '').trim();
72
+ const end = String(main.enddate ?? '').trim();
73
+ if (roomId && start && end) {
74
+ rows.push({ roomId, mtId: String(main.mt_id ?? '').trim(), start, end });
75
+ }
76
+ }
77
+ if (pageRows.length < PAGE_SIZE)
78
+ break;
79
+ }
80
+ await writeCache(env, key, rows);
81
+ return { rows, fromCache: false, ageMs: 0, requests };
82
+ }
83
+ /** 作废某天的占用缓存(提交成功后必须调,否则 5 分钟内新订的房间会被旧台账掩盖)。 */
84
+ export async function invalidateDayUsages(env, day) {
85
+ const scope = await cacheScopeSuffix(env);
86
+ await clearCache(env, usageKey(scope, day));
87
+ }
88
+ /** 直接删缓存文件(写空记录会把"刚被订满"伪装成"全天空闲",必须真删)。 */
89
+ async function clearCache(env, key) {
90
+ try {
91
+ const { rm } = await import('node:fs/promises');
92
+ const { join } = await import('node:path');
93
+ const { meetingStateDir } = await import('../env.js');
94
+ await rm(join(meetingStateDir(env.ctx), 'cache', `${key}.json`), { force: true });
95
+ }
96
+ catch {
97
+ // 删失败不影响业务:TTL 5 分钟兜底
98
+ }
99
+ }
@@ -23,9 +23,24 @@
23
23
  */
24
24
  import { meetingValidationError } from '../errors.js';
25
25
  import { meetingSuccess } from './types.js';
26
- /** ESB 动作流 id 的**内置默认值**(参考租户配置)。换租户请用入参 `flowGenerateMinutes` / `flowGenerateDocument` 覆盖。 */
27
- const FLOW_GENERATE_MINUTES = '1325287772302863633';
28
- const FLOW_GENERATE_DOCUMENT = '1325287772302863628';
26
+ /**
27
+ * ESB 动作流标识:本租户 EB 后台的 **primary_key**(32 位十六进制)。
28
+ *
29
+ * ⛔ 触发时必须走「唯一值触发」入参 `uniqueIndent`:
30
+ * `esbFlowId` 只认 19 位数字 id,传 GUID 服务端直接报
31
+ * `can not cast to long, value : 7204…`(查模板)或 `{"code":400,"msg":"系统错误"}`(触发)。
32
+ * 2026-11-03 实测:同一组 id 用 `uniqueIndent` 触发,`resultCode=200 执行成功`。
33
+ *
34
+ * 换租户请用入参 `flowGenerateMinutes` / `flowGenerateDocument` 覆盖。
35
+ */
36
+ const FLOW_GENERATE_MINUTES = '7204936B2ACC4B12A1CECD1DAB60DD97';
37
+ const FLOW_GENERATE_DOCUMENT = '1F638AE31BC746C28A1D9DCF26D91037';
38
+ /** ESB「解析纪要文档/音频附件」(primary_key)。附件解析入口,当前由后台解析任务触发,此处仅登记备查。 */
39
+ const FLOW_PARSE_ATTACHMENT = 'AD8EE1B345FD4570AE1D0882FD1B5927';
40
+ /** ESB「获取解析纪要音频结果」(primary_key)。音视频附件提取完成后由调用方按需触发。 */
41
+ const FLOW_AUDIO_RESULT = '1DB38C10699A47E89DD7CEFDAA76B8A1';
42
+ /** 智能纪要页面 id(租户级,没有接口能查;反查不到时用它兜底)。 */
43
+ const DEFAULT_AI_MINUTE_PAGE = '1250830691712172036';
29
44
  function textOf(value) {
30
45
  return value === undefined || value === null ? '' : String(value);
31
46
  }
@@ -285,14 +300,14 @@ const meetingMinuteFromGroupChat = {
285
300
  },
286
301
  flowGenerateMinutes: {
287
302
  type: 'string',
288
- pattern: '^\\d+$',
289
- description: 'ESB「根据解析文本生成智能纪要文本」流程 id。省略时用内置默认值;**换租户必须覆盖**'
290
- + '(流程 id 是租户级配置,各环境不同)',
303
+ pattern: '^(\\d+|[0-9A-Fa-f]{32})$',
304
+ description: 'ESB「根据解析文本生成智能纪要文本」的 id。可传 19 位数字 id 或 32 位十六进制 primary_key'
305
+ + '(GUID 会自动走唯一值触发 uniqueIndent)。省略时用内置默认值;换租户必须覆盖',
291
306
  },
292
307
  flowGenerateDocument: {
293
308
  type: 'string',
294
- pattern: '^\\d+$',
295
- description: 'ESB「保存解析并生成文档」流程 id。省略时用内置默认值;换租户必须覆盖',
309
+ pattern: '^(\\d+|[0-9A-Fa-f]{32})$',
310
+ description: 'ESB「保存解析并生成文档」的 id,支持数字 id 或 32 位 primary_key;省略时用内置默认值',
296
311
  },
297
312
  },
298
313
  required: ['mt_id'],
@@ -472,7 +487,8 @@ const meetingMinuteFromGroupChat = {
472
487
  // 反查失败不影响主流程
473
488
  }
474
489
  if (!aiPageId) {
475
- warnings.push('未取到 mt_minutes_ai_page(可传 aiPageId);ESB 解析该字段可能必填,缺它会失败');
490
+ aiPageId = DEFAULT_AI_MINUTE_PAGE;
491
+ warnings.push(`未从已有纪要取到 mt_minutes_ai_page,已用本租户默认值 ${DEFAULT_AI_MINUTE_PAGE}`);
476
492
  }
477
493
  }
478
494
  // ⑥ 建纪要并写原文
@@ -512,6 +528,7 @@ const meetingMinuteFromGroupChat = {
512
528
  pipeline,
513
529
  aiPageId: aiPageId || null,
514
530
  };
531
+ let documentIdFromFlow = '';
515
532
  if (pipeline === 'text') {
516
533
  result.timings = { ...timings, total: Date.now() - startedAt };
517
534
  return meetingSuccess('meeting.minute.fromGroupChat', result, warnings.length > 0 ? { warnings } : undefined);
@@ -534,7 +551,11 @@ const meetingMinuteFromGroupChat = {
534
551
  const prepareOp = getEsbOperation('esb.trigger.prepare');
535
552
  const applyOp = getEsbOperation('esb.trigger.apply');
536
553
  const trigger = async (flowId, mainTable) => {
537
- const pre = await prepareOp.handler({ employeeId, esbFlowId: flowId, customParams: { mainTable } }, esbEnv);
554
+ // 数字 id → 普通触发 esbFlowId;32 位十六进制 primary_key → 唯一值触发 uniqueIndent
555
+ const target = /^\d+$/.test(flowId)
556
+ ? { esbFlowId: flowId }
557
+ : { uniqueIndent: flowId };
558
+ const pre = await prepareOp.handler({ employeeId, ...target, customParams: { mainTable } }, esbEnv);
538
559
  return applyOp.handler({ continuation: pre?.data?.continuation, confirm: true }, esbEnv);
539
560
  };
540
561
  try {
@@ -554,7 +575,9 @@ const meetingMinuteFromGroupChat = {
554
575
  }
555
576
  else {
556
577
  try {
557
- await trigger(flowDocument, { parse_content: parseContent, mt_minute_id: minuteId });
578
+ const docResult = await trigger(flowDocument, { parse_content: parseContent, mt_minute_id: minuteId });
579
+ // 628 的返回里带 `customData.mainTable.wdID`(文档 id);回读为空时用它兜底
580
+ documentIdFromFlow = textOf(docResult?.data?.actionData?.responseData?.customData?.mainTable?.wdID);
558
581
  mark('esbDocument', stepDocStart);
559
582
  }
560
583
  catch (error) {
@@ -568,7 +591,7 @@ const meetingMinuteFromGroupChat = {
568
591
  const row = final?.data?.data ?? {};
569
592
  result.finalStatus = textOf(row.minutes_status);
570
593
  result.parseContentLength = textOf(row.parse_content).length;
571
- result.documentId = textOf(row.mt_minutes) || null;
594
+ result.documentId = textOf(row.mt_minutes) || documentIdFromFlow || null;
572
595
  result.timings = { ...timings, total: Date.now() - startedAt };
573
596
  return meetingSuccess('meeting.minute.fromGroupChat', result, warnings.length > 0 ? { warnings } : undefined);
574
597
  },
@@ -51,6 +51,19 @@ const NON_INPUT_COMPONENTS = new Set([
51
51
  const READONLY_COMPONENTS = new Set([
52
52
  'dividingline', 'paragraph', 'slidercomponent', 'button', 'subtablecontrol', 'mobileinteraction',
53
53
  ]);
54
+ /**
55
+ * 发票(`EinvoiceComponent`)/ 附件(`FileComponent`)字段也按浏览按钮口径对待。
56
+ *
57
+ * 这两个组件不在 `BROWSER_COMPONENTS` 里、也没有 `browserType`,所以 `extractFields`
58
+ * 算出的 `isBrowser` 是 false —— 但它们的值同样是**对象 id**(发票 id / 附件 fileid),
59
+ * 与浏览按钮的记录 id 同性质。凡按 `isBrowser` 分流的场合(出参 `isBrowser` 标记、
60
+ * 小e URL 参数的 `##` 前缀)都要用本判定把这两个组件补上。**只做对外口径补齐,
61
+ * 不改 `extractFields` 的 `isBrowser` 计算**——那会影响值解析/联动等内部分支,超出本判据的职责。
62
+ */
63
+ function isBrowserLikeComponentKey(componentKey) {
64
+ const normalized = String(componentKey ?? '').toLowerCase();
65
+ return normalized === 'einvoicecomponent' || normalized === 'filecomponent';
66
+ }
54
67
  /** 系统字段键:流程标题/紧急程度/密级;流程编号 REQUESTMARK 忽略不校验。 */
55
68
  const SYSTEM_FIELD_KEYS = {
56
69
  REQUEST_NAMEL: '流程标题',
@@ -2329,6 +2342,9 @@ async function buildCreateFormData(workflowId, env, options = {}) {
2329
2342
  if (field[key])
2330
2343
  item[key] = field[key];
2331
2344
  }
2345
+ // 发票 / 附件字段的值也是对象 id,对外同样标 `isBrowser`(见 isBrowserLikeComponentKey 说明)
2346
+ if (!item.isBrowser && isBrowserLikeComponentKey(field.componentKey))
2347
+ item.isBrowser = true;
2332
2348
  if (String(field.componentKey ?? '').toLowerCase() === 'employeescope')
2333
2349
  item.isEmployeeScope = true;
2334
2350
  if (String(field.componentKey ?? '').toLowerCase() === 'einvoicecomponent')
@@ -4793,6 +4809,7 @@ function xiaoEUrlParamValue(value, prefixBrowserId = false) {
4793
4809
  * **浏览按钮字段的值加 `##` 前缀**(2026-11-02 用户裁定,见 `XIAOE_BROWSER_ID_PREFIX`):这类字段解析
4794
4810
  * 出来的每一段都是对象 id,写成 `##<id>`;名称(非浏览字段的字符串)原样不加。多值/多行逐个判定,
4795
4811
  * 因此 `##<id>,<名称>`、`##<id>;<名称>` 都是合法形态。`src` 末尾的字段参数与这里同源,一起带上。
4812
+ * 发票 / 附件字段按 `isBrowserLikeComponentKey` 一并算浏览按钮口径(值同为对象 id,2026-11-03 用户裁定)。
4796
4813
  */
4797
4814
  /**
4798
4815
  * 小e 档交给浏览器页面的字段值:只留**可见且可编辑**的那些(2026-10-21 用户裁定)。
@@ -4840,11 +4857,13 @@ function xiaoECustomParams(preview, fields) {
4840
4857
  const mainValues = new Map();
4841
4858
  const detailValues = new Map();
4842
4859
  // 「这个字段是不是浏览按钮」决定要不要给对象 id 拼 `##`(2026-11-02 用户裁定)——
4843
- // 判据取 `extractFields` 算好的 `isBrowser`,与值解析走的是同一份结论,不在这里二次推断。
4860
+ // 判据取 `extractFields` 算好的 `isBrowser`,与值解析走的是同一份结论,不在这里二次推断;
4861
+ // 发票 / 附件字段不在 BROWSER_COMPONENTS(isBrowser 为 false),但其值同样是对象 id,
4862
+ // 用 isBrowserLikeComponentKey 一并补上(2026-11-03 用户裁定)。
4844
4863
  const browserFieldIds = new Set();
4845
4864
  for (const field of toArray(fields)) {
4846
4865
  const id = String(field?.id ?? '').trim();
4847
- if (id && field?.isBrowser)
4866
+ if (id && (field?.isBrowser || isBrowserLikeComponentKey(field?.componentKey)))
4848
4867
  browserFieldIds.add(id);
4849
4868
  }
4850
4869
  for (const item of toArray(preview)) {
@@ -96,6 +96,47 @@ export function validateRuleFile(raw) {
96
96
  errors.push('rules 必须是数组');
97
97
  return errors;
98
98
  }
99
+ // generalRules(2026-11-03):可选区块,只做宽松类型校验;未知键忽略(向后兼容预留扩展)。
100
+ if (file.generalRules !== undefined) {
101
+ if (!file.generalRules || typeof file.generalRules !== 'object' || Array.isArray(file.generalRules)) {
102
+ errors.push('generalRules 必须是对象');
103
+ }
104
+ else {
105
+ const identity = file.generalRules.identityFilter;
106
+ if (identity !== undefined) {
107
+ if (!identity || typeof identity !== 'object' || Array.isArray(identity)) {
108
+ errors.push('generalRules.identityFilter 必须是对象');
109
+ }
110
+ else if (identity.defaultIsremarks !== undefined
111
+ && !/^\d+(,\d+)*$/.test(String(identity.defaultIsremarks).trim())) {
112
+ errors.push('generalRules.identityFilter.defaultIsremarks 必须是「数字逗号拼接」格式,如 0,1,2');
113
+ }
114
+ if (identity?.excludedCodes !== undefined
115
+ && (!Array.isArray(identity.excludedCodes)
116
+ || identity.excludedCodes.some((c) => !/^\d+$/.test(String(c))))) {
117
+ errors.push('generalRules.identityFilter.excludedCodes 必须是数字码数组');
118
+ }
119
+ }
120
+ const priority = file.generalRules.priorityGroup;
121
+ if (priority !== undefined) {
122
+ if (!priority || typeof priority !== 'object' || Array.isArray(priority)) {
123
+ errors.push('generalRules.priorityGroup 必须是对象');
124
+ }
125
+ else {
126
+ if (priority.title !== undefined && (typeof priority.title !== 'string' || !priority.title.trim())) {
127
+ errors.push('generalRules.priorityGroup.title 必须是非空字符串');
128
+ }
129
+ if (priority.rules !== undefined) {
130
+ const KNOWN_PRIORITY_RULES = ['overdue', 'levelFallback'];
131
+ if (!Array.isArray(priority.rules) || priority.rules.length === 0
132
+ || priority.rules.some((r) => !KNOWN_PRIORITY_RULES.includes(String(r)))) {
133
+ errors.push(`generalRules.priorityGroup.rules 只支持 ${KNOWN_PRIORITY_RULES.join('、')}`);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
99
140
  const defaultsExclude = validateExcludeFields(file.defaults?.excludeOriginFormDataFields, 'defaults.excludeOriginFormDataFields');
100
141
  if (defaultsExclude)
101
142
  errors.push(defaultsExclude);
@@ -253,7 +294,13 @@ export function loadRules(options = {}) {
253
294
  }
254
295
  }
255
296
  return {
256
- file: { schemaVersion: base.file.schemaVersion, defaults: { ...FALLBACK_DEFAULTS, ...base.file.defaults }, rules },
297
+ file: {
298
+ schemaVersion: base.file.schemaVersion,
299
+ defaults: { ...FALLBACK_DEFAULTS, ...base.file.defaults },
300
+ rules,
301
+ // 通用规则区(2026-11-03):仅取 default.json 的定义;用户意图文件不参与通用规则
302
+ ...(base.file.generalRules ? { generalRules: base.file.generalRules } : {}),
303
+ },
257
304
  warnings,
258
305
  dir,
259
306
  };
@@ -472,6 +519,10 @@ export function activeRuleFor(workflowName) {
472
519
  return null;
473
520
  return resolveRule(activeRules(), name)?.rule ?? null;
474
521
  }
522
+ /** 通用规则区(2026-11-03):未配置返回 null,消费方按内置默认兜底。 */
523
+ export function generalRules() {
524
+ return activeRules().generalRules ?? null;
525
+ }
475
526
  /** 清空进程内缓存(单测用)。 */
476
527
  export function resetActiveRules() {
477
528
  activeFile = null;
@@ -123,19 +123,20 @@ export function promptTagOf(prompt) {
123
123
  return String(prompt ?? '').trim();
124
124
  }
125
125
  /**
126
- * 缓存条目是否与当前目标字数 / 口径 / 详情素材匹配。
126
+ * 缓存条目是否与当前口径/素材匹配。
127
127
  *
128
- * - 不传 `maxChars` 时不按字数过滤;传入后必须与写入口径一致;
129
- * - `pk`(口径特征值)同理:规则改了 `requestSummaryPrompt` ⇒ pk 变化 ⇒ 旧摘要自动失效;
130
- * - `dv`(详情素材特征值,2026-11-01 根治):
131
- * 条目没有 dv = 旧条目,兼容命中;
132
- * 条目有 dv 时必须与当前素材特征值一致——当前**没有**详情素材(无法核验)也视为失效
133
- * (与「缓存没了就当从未生成过、直接走实时逻辑」的口径一致)。
128
+ * **失效条件(2026-11-02 用户定稿):只由 `requestSummaryPrompt`(default.json / 用户意图)调整触发**——
129
+ * - `pk`:双方都存在时才比对;**条目缺 pk = 旧口径条目,兼容命中**(无法证明口径变过就不作废);
130
+ * - `maxChars`:双方都存在时才比对,且调用方只在用户**显式**传字数时才传入
131
+ * (`summary.ts` 的 `explicitMaxCharsFor`)——规则/默认字数变化不作废历史摘要;
132
+ * 用户显式要求「10 字以内」时仍会重写(2026-10-22 实测踩坑的保护保留);
133
+ * - `dv`:详情素材特征值仍比对——素材升级 / 排除清单调整后旧摘要缺信息(如安全工单缺行号),
134
+ * 不能继续命中;条目缺 dv = 旧条目,兼容命中。
134
135
  */
135
136
  function entryMatches(entry, maxChars, pk, dv) {
136
- if (maxChars !== undefined && (entry.maxChars ?? 50) !== maxChars)
137
+ if (pk !== undefined && entry.pk !== undefined && entry.pk !== pk)
137
138
  return false;
138
- if (pk !== undefined && (entry.pk ?? '') !== pk)
139
+ if (maxChars !== undefined && entry.maxChars !== undefined && entry.maxChars !== maxChars)
139
140
  return false;
140
141
  if (entry.dv !== undefined && entry.dv !== dv)
141
142
  return false;
@@ -1,4 +1,7 @@
1
- import { activeRuleFor } from './aigroupRules.js';
1
+ import { activeRuleFor, generalRules } from './aigroupRules.js';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
2
5
  import { WORKFLOW_ENDPOINTS } from '../../endpoints.js';
3
6
  import { saveSearchIndex } from '../../state.js';
4
7
  import { buildSearchQuery } from '../read.js';
@@ -19,16 +22,26 @@ export async function collectRawTodos(args, env) {
19
22
  // 取数阶段只允许 getTodoListData,getTodo* 分页/统计接口一律拦掉。
20
23
  const listEnv = guardListOnlyHost(env);
21
24
  const searchArgs = { category };
22
- for (const key of ['tabid', 'tabName', 'subCategory', 'workflow', 'creator', 'requestName', 'receiveTime', 'requestLevel']) {
25
+ for (const key of ['tabid', 'tabName', 'subCategory', 'isremarks', 'workflow', 'creator', 'requestName', 'receiveTime', 'requestLevel']) {
23
26
  const value = args[key];
24
27
  if (value !== undefined && value !== null && value !== '')
25
28
  searchArgs[key] = value;
26
29
  }
30
+ // 通用规则(2026-11-03):默认排除抄送类身份(generalRules.identityFilter.defaultIsremarks)。
31
+ // 用户显式传 isremarks 时不覆盖;「包含抄送 / 只要抄送」等诉求由调用方按提示词显式传 --isremarks。
32
+ // 注意:warnings 在下方声明,默认值注入放到声明之后(见「通用规则默认身份过滤」块)。
27
33
  // 显式给 tabid 才走平铺路径(见 DEFAULT_TABID 注释);用户另有子分类条件时原样尊重。
28
34
  const explicitTab = ['tabid', 'tabName', 'subCategory'].some((key) => searchArgs[key] !== undefined && String(searchArgs[key]).trim() !== '');
29
35
  if (!explicitTab)
30
36
  searchArgs.tabid = DEFAULT_TABID;
31
37
  const warnings = [];
38
+ if (searchArgs.isremarks === undefined) {
39
+ const defaultIsremarks = generalRules()?.identityFilter?.defaultIsremarks?.trim();
40
+ if (defaultIsremarks) {
41
+ searchArgs.isremarks = defaultIsremarks;
42
+ warnings.push(`已按通用规则默认排除抄送类身份(isremarks=${defaultIsremarks});如需包含抄送,请在提示词中说明`);
43
+ }
44
+ }
32
45
  if (cappedFrom !== undefined) {
33
46
  // 超过上限时显式告知,避免调用方误以为已取全量。
34
47
  warnings.push(`单次最多只能取 ${MAX_FETCH_LIMIT} 条(${MAX_FETCH_PAGES} 次请求 × ${FETCH_PAGE_SIZE} 条),`
@@ -50,6 +63,19 @@ export async function collectRawTodos(args, env) {
50
63
  const listPath = LIST_ENDPOINT_BY_CATEGORY[category] ?? WORKFLOW_ENDPOINTS.TODO_LIST;
51
64
  for (let pageNo = 1; pageNo <= plannedPages; pageNo += 1) {
52
65
  const { query } = await buildSearchQuery(listEnv, { ...searchArgs, current: pageNo, pageSize }, 'list');
66
+ // 2026-11-02:isremarks(备注筛选,多个数字英文逗号拼接)只由汇总链路透传,放 params 层。
67
+ // 注入点故意放在 collect 内部而非共享的 buildSearchQuery,保证 workflow.search 的代码路径零改动。
68
+ const isremarks = String(searchArgs.isremarks ?? '').trim();
69
+ if (isremarks !== '') {
70
+ query.params = { ...(query.params || {}), isremarks };
71
+ }
72
+ // 调试留痕(环境变量开启才写):把最终发往列表接口的 body 原样落盘,便于核对拼接结果。
73
+ if (process.env.WEAVER_WORK_CLI_DUMP_LIST_BODY) {
74
+ try {
75
+ writeFileSync(join(tmpdir(), `weaver-list-body-page${pageNo}.json`), JSON.stringify({ path: listPath, body: query }, null, 2));
76
+ }
77
+ catch { /* 留痕失败不影响主流程 */ }
78
+ }
53
79
  const response = await listEnv.host.workflowRequest({
54
80
  method: 'POST',
55
81
  path: listPath,
@@ -154,6 +154,7 @@ export const ALLOWED_INPUT_KEYS = [
154
154
  'tabid',
155
155
  'tabName',
156
156
  'subCategory',
157
+ 'isremarks',
157
158
  'workflow',
158
159
  'creator',
159
160
  'requestName',
@@ -1,4 +1,5 @@
1
1
  import { workflowValidationError } from '../../errors.js';
2
+ import { generalRules } from './aigroupRules.js';
2
3
  import { UNKNOWN_GROUP, groupKeyOf, readGroupBy, readItems, readTopN, assertKnownKeys, } from './todoSmartConvert.core.js';
3
4
  import { intentAppliesToKind, } from './businessRules.js';
4
5
  /** 紧急程度以 requestLevel=2 或显示文本「紧急」为准;1 级是「重要」。 */
@@ -115,29 +116,32 @@ export function isExpiringTodayRow(row, now = Date.now()) {
115
116
  return isExpiringTodayUserOverTime(row.userOverTime, now);
116
117
  }
117
118
  /**
118
- * 「建议优先处理的流程」筛选规则:
119
+ * 「建议优先处理的流程」筛选规则(2026-11-03 起可在 `generalRules.priorityGroup.rules` 配置启用子集):
119
120
  *
120
- * 1. **超时** 必收(`userOverTime` / `overTime` 判定,与紧急程度无关);
121
- * 2. **紧急程度**按 `requestLevel` 取:整批有 `=2`(紧急)就只要 2;没有 2 才退到 `=1`(重要);
121
+ * 1. **超时**(`overdue`)必收(`userOverTime` / `overTime` 判定,与紧急程度无关);
122
+ * 2. **紧急程度**(`levelFallback`)按 `requestLevel` 取:整批有 `=2`(紧急)就只要 2;没有 2 才退到 `=1`(重要);
122
123
  * `=0`(正常)**不因等级入选**——降级链到 1 就停,不倒到 0;
123
124
  * 3. 整批连 1 级都没有(全 0)时,等级条件不生效,只按超时筛;此时若一条都没超时,
124
125
  * 优先组为空("实在没有紧急的就算了"),不硬凑。
125
126
  *
126
127
  * 注意这是**筛选**,不是视图叠加:入选条目的 `requestLevel` 只有 2(或 2 缺失时的 1)与超时两类。
128
+ * `enabledRules` 缺省 = 全部启用(与历史行为一致);预留 `expiringToday`(今天到期)等后续扩展。
127
129
  */
128
- export function selectPriorityRows(rows) {
130
+ export function selectPriorityRows(rows, enabledRules) {
131
+ const rules = enabledRules ?? new Set(['overdue', 'levelFallback']);
129
132
  let maxLevel = 0;
130
133
  for (const row of rows) {
131
134
  if (row.requestLevel > maxLevel)
132
135
  maxLevel = row.requestLevel;
133
136
  }
134
- const targetLevel = maxLevel >= 2 ? 2 : maxLevel >= 1 ? 1 : undefined;
137
+ const targetLevel = rules.has('levelFallback') && maxLevel >= 2 ? 2
138
+ : rules.has('levelFallback') && maxLevel >= 1 ? 1 : undefined;
135
139
  const selected = [];
136
140
  let overdueCount = 0;
137
141
  let levelCount = 0;
138
142
  let bothCount = 0;
139
143
  for (const row of rows) {
140
- const overdue = isOverdueRow(row);
144
+ const overdue = rules.has('overdue') && isOverdueRow(row);
141
145
  const byLevel = targetLevel !== undefined && row.requestLevel >= targetLevel;
142
146
  if (overdue)
143
147
  overdueCount += 1;
@@ -324,7 +328,11 @@ export function runGroup(args) {
324
328
  const groupBy = readGroupBy(args.groupBy);
325
329
  const topN = readTopN(args.topN);
326
330
  // 优先组是叠加视图:筛出的条目**不从**工作流分组剔除,两个视图各自完整。
327
- const priority = selectPriorityRows(rows);
331
+ // 2026-11-03:标题与入选规则可在 default.json 的 generalRules.priorityGroup 配置;缺省 = 内置全量规则。
332
+ const pgConfig = generalRules()?.priorityGroup ?? null;
333
+ const pgTitle = pgConfig?.title?.trim() || PRIORITY_GROUP_TITLE;
334
+ const pgRules = new Set(pgConfig?.rules ?? ['overdue', 'levelFallback']);
335
+ const priority = selectPriorityRows(rows, pgRules);
328
336
  const priorityRows = sortPriorityRows(priority.rows);
329
337
  const restRows = rows;
330
338
  const buckets = new Map();
@@ -360,7 +368,7 @@ export function runGroup(args) {
360
368
  ? [{
361
369
  id: PRIORITY_GROUP_ID,
362
370
  topPriority: true,
363
- title: PRIORITY_GROUP_TITLE,
371
+ title: pgTitle,
364
372
  count: priorityRows.length,
365
373
  requestDatas: priorityRows.map((row) => toRequestData(row)),
366
374
  stats: statsOf(priorityRows),
@@ -392,7 +400,7 @@ export function runGroup(args) {
392
400
  : priority.targetLevel === 1
393
401
  ? '重要(requestLevel=1;本次无 2 级数据,已按规则降级取 1,0 级不取)'
394
402
  : '无等级(本次整批没有 ≥1 级的条目,只按超时筛)';
395
- warnings.push(`「${PRIORITY_GROUP_TITLE}」筛出 ${priorityRows.length} 条:超时 ${priority.overdueCount} 条 / ` +
403
+ warnings.push(`「${pgTitle}」筛出 ${priorityRows.length} 条:超时 ${priority.overdueCount} 条 / ` +
396
404
  `等级命中 ${priority.levelCount} 条(两者都满足 ${priority.bothCount} 条),等级口径=${levelText};` +
397
405
  `这 ${priorityRows.length} 条同时保留在各自工作流分组中(叠加视图:Σcount 会大于 totalCount,` +
398
406
  `totalCount 按去重条数计 = ${rows.length})`);
@@ -80,6 +80,7 @@ const inputSchema = schemaOf({
80
80
  tabid: { type: 'string' },
81
81
  tabName: { type: 'string' },
82
82
  subCategory: { type: 'string' },
83
+ isremarks: { type: 'string', description: '备注筛选(getTodoListData params.isremarks),多个数字用英文逗号拼接,仅作用于本汇总取数' },
83
84
  workflow: { type: 'array', items: { type: 'string' } },
84
85
  creator: { type: 'string' },
85
86
  requestName: { type: 'string' },
@@ -20,6 +20,18 @@ function needAiSummary(workflowName) {
20
20
  function requestMaxCharsFor(args, workflowName) {
21
21
  return readBoundedInt(args.summaryMaxChars ?? activeRuleFor(workflowName)?.summaryMaxChars, 'summaryMaxChars', DEFAULT_REQUEST_SUMMARY_MAX, 10, 500);
22
22
  }
23
+ /**
24
+ * 缓存**命中比对**用的字数口径:只在用户显式传了 `summaryMaxChars`(`--summary-chars`)时返回,
25
+ * 否则返回 undefined(= 不按字数过滤)。
26
+ * 2026-11-02 用户定稿:缓存失效只由 requestSummaryPrompt 调整触发;
27
+ * 规则/默认字数变化(如 50→150)不作废历史摘要,只有用户显式要求不同字数时才重写。
28
+ */
29
+ function explicitMaxCharsFor(args, workflowName) {
30
+ const explicit = args.summaryMaxChars;
31
+ if (explicit === undefined || explicit === null || String(explicit).trim() === '')
32
+ return undefined;
33
+ return requestMaxCharsFor(args, workflowName);
34
+ }
23
35
  /** 该组的单条摘要口径提示(规则 `requestSummaryPrompt`,空串表示没有)。 */
24
36
  function requestPromptFor(workflowName) {
25
37
  return (activeRuleFor(workflowName)?.requestSummaryPrompt ?? '').trim();
@@ -247,7 +259,7 @@ export async function runSummaryContext(args, env, prefetched) {
247
259
  for (const unit of units) {
248
260
  for (const row of unit.rows) {
249
261
  const name = ruleNameForRow(unit, row);
250
- const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, requestMaxCharsFor(args, name), promptTagOf(requestPromptFor(name)), materialTagOfRow(row.requestId, row.lastFeedBackDateTime));
262
+ const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, explicitMaxCharsFor(args, name), promptTagOf(requestPromptFor(name)), materialTagOfRow(row.requestId, row.lastFeedBackDateTime));
251
263
  if (cached) {
252
264
  row.summary = cached;
253
265
  prefilled += 1;
@@ -490,7 +502,7 @@ export async function runSummary(args, env) {
490
502
  if (!row?.requestId || (row.summary ?? '').trim())
491
503
  continue;
492
504
  const name = ruleNameForGroup(group, row);
493
- const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, maxCharsFor(name), promptTagOf(requestPromptFor(name)), materialTagOfRow(row.requestId, row.lastFeedBackDateTime));
505
+ const cached = summaryCache.get(row.requestId, row.lastFeedBackDateTime, explicitMaxCharsFor(args, name), promptTagOf(requestPromptFor(name)), materialTagOfRow(row.requestId, row.lastFeedBackDateTime));
494
506
  if (cached) {
495
507
  row.summary = cached;
496
508
  }
package/docs/e10-auth.md CHANGED
@@ -59,6 +59,19 @@ Chrome/Edge 原生确认框。宿主需要返回特定会话时,可以传
59
59
  结束后页面会自动退回浏览器自定义协议打开方式,不直接显示网络请求错误。
60
60
  智能小 E 统一登录不会经过浏览器,也不会显示“返回智能小 E”弹层;该弹层只属于用户另行显式发起的 `auth login` 浏览器 OAuth。
61
61
 
62
+ HTTPS E10 授权页跳转到 HTTP loopback 是桌面 OAuth 的标准回调方式:它属于浏览器
63
+ 顶层导航,不按 Mixed Content 子资源请求处理。但部分 E10 环境或浏览器安全策略会把
64
+ 回调改成 `https://127.0.0.1:<port>`;由于 CLI 本地监听器是 HTTP,此时浏览器会显示
65
+ `ERR_SSL_PROTOCOL_ERROR`,而旧版本 CLI 会继续等待直到超时。CLI 现在会识别发到 HTTP
66
+ 端口的 TLS ClientHello,在 WorkBuddy/终端立即给出协议错配提示,并保留 30 秒恢复窗口:
67
+ 只把浏览器当前地址开头明确改成 `http://`,保留完整路径和查询参数后回车,即可让同一次
68
+ 登录继续完成;未处理则返回 `authentication/oauth_callback_protocol_mismatch`,不再挂起
69
+ 5 分钟。长期应在 E10 认证服务侧保留传入的 HTTP loopback 回调,不要改写为 HTTPS。
70
+ 若仍收不到回调,再检查 CLI 与浏览器是否在同一台机器,以及 E10 侧的回调白名单。
71
+ 该排查只适用于显式浏览器登录,
72
+ `auth xiaoe` 和业务命令的 XiaoE 自动取登录信息使用独立处理函数,不创建本地 HTTP
73
+ 服务,也不校验或使用 callback 端口。
74
+
62
75
  XiaoE 宿主身份有两种判定入口:桌面客户端由 Agent 根据“智能小 e 客户端”“小 E”、`XiaoE`、`xiaoe-client` 等明确上下文判断,并传入 `--runtime-agent xiaoe`;网页端沿用原有环境变量规则,成对的 `XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 会被识别为 XiaoE。工作目录、客户端文件、内置 Node.js、历史登录、路径变量或单个凭据变量都不能单独用于判定。判定为 XiaoE 后,Agent 显式执行 `weaver-work-cli auth xiaoe`。
63
76
 
64
77
  CLI 依次尝试成对的平台凭据、npm 包自带的 `scripts/xiaoe_env.py` 兼容路径、宿主 `xiaoeUserData/tokens.py`、桌面 XiaoE 客户端本地会话和 `WEAVER_XIAOE_*` ETEAMSID 映射。客户端会话优先读取 `XIAOE_USER_SETTINGS_PATH`,再从 `XIAOE_ROBOT_DATA_ROOT` / `XIAOE_ROBOT_ROOT` 查找,最后回退 macOS `~/Library/Application Support/XiaoE/Robots/<id>/user_settings.json` 或 Windows `%LOCALAPPDATA%/XiaoE/Robots/<id>/user_settings.json`。这些变量和文件只用于定位凭据,不能用于识别宿主。`XIAOE_DOMAIN` 是 XiaoE/erpa 换票服务地址,不是 E10 登录地址。
@@ -69,6 +82,8 @@ E10 目标地址按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*` E10 配置
69
82
 
70
83
  `auth xiaoe` 会先打印不含凭证的 preflight 摘要,给出 `authMode=xiaoe`、`credentialSourceDetected`、`attempted`、各凭据源检测结果和目标 `baseUrl`。进入脚本分支时会打印 `Trying XiaoE token script credential...`;进入内置 Node 会话分支时会打印 `Trying built-in XiaoE client session credential...`。成功结果和后续 `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、`credentialSourceLabel`。
71
84
 
85
+ XiaoE 换票结果中的 `expiryTimes` 按 Unix 毫秒时间戳持久化。宿主脚本未返回该字段时,CLI 与 XiaoE `tokens.py` 保持一致,按换票成功时间加 2 小时生成。业务命令加载会话后或长进程每次请求前发现当前时间达到 `expiryTimes` 时,会先静默重新换票;换票失败则停止本次业务请求,不会继续发送已知过期的 ETEAMSID。
86
+
72
87
  业务命令缺少可读取的本地会话时,若当前运行时已通过 `XIAOE_*`、`--runtime-agent xiaoe` 等标记识别为 XiaoE,公共会话加载层会先静默尝试统一登录,成功后继续业务操作。失败时返回 `authentication/xiaoe_credential_unavailable`,并明确提示已尝试自动获取。显式 `--auth` 不触发此恢复;未识别到 XiaoE 的首次认证仍需显式选择登录方式。`auth status --no-check` 等本地状态查询不会主动登录。已有 XiaoE 会话被 E10 接口明确拒绝为失效时,公共请求运行时会根据持久化会话的 `agentType` / `loginSource` 静默换票并重放原请求一次;这条路径不依赖 shared Skill、Agent、`XIAOE_*` 或 `--runtime-agent xiaoe`。它不会打印中间登录步骤,不会自动打开浏览器,也不会对流式请求、网络错误、`partial` / `write_uncertain` 做重放。`--runtime-agent xiaoe` 表示当前运行时身份,不携带凭据。WorkBuddy、Codex 及其他智能体首次认证使用 `auth login --base-url <url> --agent_type <agent_type>` 的浏览器 OAuth 流程。
73
88
 
74
89
  无法使用浏览器回调时,可以用 `auth set` 直接写入浏览器中复制到的
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weaver-work-cli",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "面向泛微工作工具链的可扩展 TypeScript CLI 基础框架(E10 业票通等能力)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",