weaver-work-cli 0.1.39 → 0.1.42

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 (197) hide show
  1. package/README.md +4 -2
  2. package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
  3. package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
  4. package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
  5. package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
  6. package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
  7. package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
  8. package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
  9. package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
  10. package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
  11. package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
  12. package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
  13. package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
  14. package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
  15. package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
  16. package/dist/chunks/chunk-BIPYLLVS.js +501 -0
  17. package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
  18. package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
  19. package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
  20. package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
  21. package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
  22. package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
  23. package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
  24. package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
  25. package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
  26. package/dist/chunks/chunk-NKZRQXST.js +2546 -0
  27. package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
  28. package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
  29. package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
  30. package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
  31. package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
  32. package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
  33. package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
  34. package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
  35. package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
  36. package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
  37. package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
  38. package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
  39. package/dist/chunks/env-LB7NBL2O.js +19 -0
  40. package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
  41. package/dist/chunks/factory-6ZFQXQ46.js +30 -0
  42. package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
  43. package/dist/chunks/group-chat-2HHAJN46.js +20 -0
  44. package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
  45. package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
  46. package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
  47. package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
  48. package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
  49. package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
  50. package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
  51. package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
  52. package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
  53. package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
  54. package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
  55. package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
  56. package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
  57. package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
  58. package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
  59. package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
  60. package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
  61. package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
  62. package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
  63. package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
  64. package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
  65. package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
  66. package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
  67. package/dist/index.js +33 -28
  68. package/dist/internal/e10/auth/commands.js +110 -56
  69. package/dist/internal/e10/auth/messages.js +6 -0
  70. package/dist/internal/e10/auth/session.js +28 -1
  71. package/dist/internal/e10/auth/xiaoe.js +315 -48
  72. package/dist/internal/e10/auth-recovery.js +36 -5
  73. package/dist/internal/e10/client.js +13 -0
  74. package/dist/internal/e10/context.js +61 -9
  75. package/dist/internal/e10/index.js +1 -1
  76. package/dist/internal/e10/network-error.js +93 -0
  77. package/dist/internal/e10/request-runtime.js +15 -0
  78. package/dist/shortcuts/calendar/operations/read.js +31 -22
  79. package/dist/shortcuts/calendar/operations/write.js +45 -1
  80. package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
  81. package/dist/shortcuts/cowork/operations/item-write.js +5 -3
  82. package/dist/shortcuts/cowork/operations/member-write.js +3 -1
  83. package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
  84. package/dist/shortcuts/cowork/operations/shared.js +7 -0
  85. package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
  86. package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
  87. package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
  88. package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
  89. package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
  90. package/dist/shortcuts/ehr/operations/recruit.js +10 -4
  91. package/dist/shortcuts/ehr/operations/shared.js +47 -0
  92. package/dist/shortcuts/hrm/operations/org.js +118 -0
  93. package/dist/shortcuts/index.js +5 -0
  94. package/dist/shortcuts/mail/operations/mail-send.js +8 -2
  95. package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
  96. package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
  97. package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
  98. package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
  99. package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
  100. package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
  101. package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
  102. package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
  103. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
  104. package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
  105. package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
  106. package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
  107. package/dist/shortcuts/project/operations/read.js +6 -25
  108. package/dist/shortcuts/project/operations/shared.js +22 -0
  109. package/dist/shortcuts/project/operations/write.js +28 -2
  110. package/dist/shortcuts/recruit-stage/context.js +119 -0
  111. package/dist/shortcuts/recruit-stage/continuation.js +73 -0
  112. package/dist/shortcuts/recruit-stage/errors.js +65 -0
  113. package/dist/shortcuts/recruit-stage/host.js +109 -0
  114. package/dist/shortcuts/recruit-stage/index.js +115 -0
  115. package/dist/shortcuts/recruit-stage/manifest.js +29 -0
  116. package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
  117. package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
  118. package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
  119. package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
  120. package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
  121. package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
  122. package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
  123. package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
  124. package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
  125. package/dist/shortcuts/recruit-stage/shared.js +608 -0
  126. package/dist/shortcuts/workflow/operations/form.js +21 -2
  127. package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
  128. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
  129. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
  130. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
  131. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
  132. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
  133. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
  134. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
  135. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
  136. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
  137. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
  138. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
  139. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
  140. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
  141. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
  142. package/docs/e10-auth.md +22 -1
  143. package/docs/operation-manual.md +8 -0
  144. package/package.json +1 -1
  145. package/scripts/install-skill-to-xiaoe.mjs +105 -0
  146. package/scripts/mcp-meeting.mjs +234 -0
  147. package/scripts/package-skill.mjs +4 -2
  148. package/skill-template/business-info.json +24 -0
  149. package/skill-template/domains/recruit-stage.md +29 -0
  150. package/skills/weaver-e10-calendar/SKILL.md +2 -1
  151. package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
  152. package/skills/weaver-e10-cowork/SKILL.md +1 -1
  153. package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
  154. package/skills/weaver-e10-jucailin/SKILL.md +1 -1
  155. package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
  156. package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
  157. package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
  158. package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
  159. package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
  160. package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
  161. package/skills/weaver-e10-mail/SKILL.md +1 -1
  162. package/skills/weaver-e10-mail/references/mail-send.md +2 -0
  163. package/skills/weaver-e10-meeting/SKILL.md +119 -26
  164. package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
  165. package/skills/weaver-e10-meeting.zip +0 -0
  166. package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
  167. package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
  168. package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
  169. package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
  170. package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
  171. package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
  172. package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
  173. package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
  174. package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
  175. package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
  176. package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
  177. package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
  178. package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
  179. package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
  180. package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
  181. package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
  182. package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
  183. package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
  184. package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
  185. package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
  186. package/skills/weaver-e10-shared/SKILL.md +2 -2
  187. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
  188. package/skills/weaver-e10-shijingran/SKILL.md +3 -2
  189. package/skills/weaver-e10-shijingran/references/operations.md +3 -1
  190. package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
  191. package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
  192. package/skills/weaver-e10-workflow/SKILL.md +7 -1
  193. package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
  194. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
  195. package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
  196. package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
  197. package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
@@ -4,8 +4,14 @@ import { dirname, resolve } from 'node:path';
4
4
  import { CliError, ExitCode } from '../../../core/errors.js';
5
5
  import { isConnectorMode } from '../../../core/connector.js';
6
6
  import { writeWithRetry } from '../../../core/write-with-retry.js';
7
+ import { createTimeoutSignal } from '../network-error.js';
7
8
  import { encryptAuthData, parseAuthFile } from './crypto.js';
8
9
  import { getE10ActiveProfilePath, getE10ProfilesRoot } from './paths.js';
10
+ /**
11
+ * teamsCheck 与会话探测都是「发业务请求之前的必经往返」,没有上限时一个半开
12
+ * 连接就能把整条命令挂死。所有这类小请求统一用这个超时。
13
+ */
14
+ export const E10_TEAMS_CHECK_TIMEOUT_MS = 15_000;
9
15
  const RSA_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
10
16
  MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEOk/WyyJKdAHIfMODFnKg7MBoraFBnL3F18hhLx5n01J5g4hCkmpc5+KQFKIQIA/QWk9m7QBzI/OV9ZDFk54LaWm3xP8C+P1TPp7UVGz3iNDLdvgSxuGEYPmML3NaqkbVlvW7/NlNA3ED4Kao7rR5GUYSdFfbaBoxBFae638hzQIDAQAB
11
17
  -----END PUBLIC KEY-----`;
@@ -337,6 +343,15 @@ export class E10AuthSession {
337
343
  get credentialSourceLabel() {
338
344
  return this.data.credentialSourceLabel || '';
339
345
  }
346
+ get expiryTimes() {
347
+ const value = Number(this.data.expiryTimes);
348
+ return Number.isFinite(value) && value > 0 ? value : undefined;
349
+ }
350
+ /** XiaoE 换取的 ETEAMSID 是否已经超过其明确的失效时间。 */
351
+ isExpired(now = Date.now()) {
352
+ const expiryTimes = this.expiryTimes;
353
+ return expiryTimes !== undefined && now >= expiryTimes;
354
+ }
340
355
  getBaseUrl() {
341
356
  return (this.data.baseUrl || '').replace(/\/$/, '');
342
357
  }
@@ -389,6 +404,7 @@ export class E10AuthSession {
389
404
  stTicket: this.data.stTicket,
390
405
  redirectUrl: this.data.redirectUrl,
391
406
  cookieSavedAt: this.data.cookieSavedAt,
407
+ expiryTimes: this.expiryTimes,
392
408
  mainUrl: this.data.mainUrl,
393
409
  ...sourceInfo,
394
410
  hasEteamsId: Boolean(this.eteamsId),
@@ -419,12 +435,14 @@ export class E10AuthSession {
419
435
  return null;
420
436
  const session = E10AuthSession.fromFile(authPath, baseUrl, passportUrl);
421
437
  // 富化只是"刷新/纠偏":登录时这些字段已落盘。调用方可显式跳过(见 E10SessionLoadOptions)。
422
- if (session && !(load.skipProfileEnrichment && hasUsableUserId(session))) {
438
+ if (session && !session.isExpired() && !(load.skipProfileEnrichment && hasUsableUserId(session))) {
423
439
  await session.fetchEmployeeId();
424
440
  }
425
441
  return session;
426
442
  }
427
443
  async fetchEmployeeId() {
444
+ // 富化不是关键路径:超时就放弃,不能让 teamsCheck 无限等待。
445
+ const { signal, clear } = createTimeoutSignal(E10_TEAMS_CHECK_TIMEOUT_MS);
428
446
  try {
429
447
  const resp = await fetch(`${this.getBaseUrl()}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(this.getBaseUrl())}`, {
430
448
  method: 'POST',
@@ -436,6 +454,7 @@ export class E10AuthSession {
436
454
  'user-agent': this.uaHeader(),
437
455
  origin: this.getBaseUrl(),
438
456
  },
457
+ signal,
439
458
  });
440
459
  const data = await resp.json().catch(() => ({}));
441
460
  const userInfo = readTeamsCheckUserInfo(resp, data);
@@ -455,8 +474,12 @@ export class E10AuthSession {
455
474
  catch {
456
475
  // Non-critical profile enrichment.
457
476
  }
477
+ finally {
478
+ clear();
479
+ }
458
480
  }
459
481
  async checkValid() {
482
+ const { signal, clear } = createTimeoutSignal(E10_TEAMS_CHECK_TIMEOUT_MS);
460
483
  try {
461
484
  const resp = await fetch(`${this.getBaseUrl()}/api/ecode/tree/list?parentId=0`, {
462
485
  headers: {
@@ -466,12 +489,16 @@ export class E10AuthSession {
466
489
  'user-agent': this.uaHeader(),
467
490
  },
468
491
  redirect: 'manual',
492
+ signal,
469
493
  });
470
494
  return resp.status === 200;
471
495
  }
472
496
  catch {
473
497
  return false;
474
498
  }
499
+ finally {
500
+ clear();
501
+ }
475
502
  }
476
503
  async ensureValid() {
477
504
  if (await this.checkValid())
@@ -5,7 +5,8 @@ import { join } from 'node:path';
5
5
  import { CliError, ExitCode } from '../../../core/errors.js';
6
6
  import { packagePath } from '../../../core/package-paths.js';
7
7
  import { isXiaoERuntime, isXiaoERuntimeAgent } from '../../../core/runtime-agent.js';
8
- import { buildE10CookieHeader, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
8
+ import { createTimeoutSignal, isAbortError, isNetworkErrorCode, looksLikeTransportMessage, readErrorCauseCode, } from '../network-error.js';
9
+ import { buildE10CookieHeader, E10_TEAMS_CHECK_TIMEOUT_MS, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
9
10
  export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
10
11
  export const XIAOE_AGENT_TYPE = 'XiaoE';
11
12
  // `XIAOE_` 是宿主/平台保留前缀(XIAOE_DOMAIN、XIAOE_USER_TOKEN、XIAOE_SKILL_* 等)。
@@ -57,6 +58,111 @@ export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/ex
57
58
  export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
58
59
  export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
59
60
  export const XIAOE_BUNDLED_ENV_SCRIPT_SOURCE = 'bundled scripts/xiaoe_env.py';
61
+ /** 与 XiaoE `tokens.py` 保持一致:换票成功后默认有效 2 小时。 */
62
+ export const XIAOE_ETEAMID_TTL_MS = 7_200_000;
63
+ const XIAOE_DIAGNOSTIC_MAX_LENGTH = 300;
64
+ /** 换票失败原因可能夹带服务端回显,落进结构化错误前先抹掉已知密钥。 */
65
+ function redactXiaoEDiagnostic(message, secrets) {
66
+ let text = message;
67
+ for (const secret of secrets) {
68
+ if (secret && secret.length >= 8 && text.includes(secret)) {
69
+ text = text.split(secret).join('<redacted>');
70
+ }
71
+ }
72
+ const normalized = text.replace(/\s+/gu, ' ').trim();
73
+ return normalized.length > XIAOE_DIAGNOSTIC_MAX_LENGTH
74
+ ? `${normalized.slice(0, XIAOE_DIAGNOSTIC_MAX_LENGTH)}…`
75
+ : normalized;
76
+ }
77
+ function noteXiaoEStep(diagnostics, step) {
78
+ diagnostics?.attemptedSteps.push(step);
79
+ }
80
+ function tagXiaoEError(message, tags) {
81
+ return Object.assign(new CliError(message, ExitCode.AuthError), tags);
82
+ }
83
+ function readTag(error, key, type) {
84
+ if (!error || typeof error !== 'object')
85
+ return undefined;
86
+ const value = error[key];
87
+ return typeof value === type ? value : undefined;
88
+ }
89
+ /** 401/403/3xx = 登录态被拒;其余 4xx/5xx 才算服务端 HTTP 异常。 */
90
+ function classifyXiaoEHttpStatus(status) {
91
+ if (status === 401 || status === 403)
92
+ return 'credential';
93
+ if (status >= 300 && status < 400)
94
+ return 'credential';
95
+ return status >= 400 ? 'http' : 'credential';
96
+ }
97
+ /**
98
+ * 把任意异常归成三类。已经带标签的错误直接沿用;原始 fetch 异常则顺着
99
+ * `error.cause.code`(Node 把真实原因藏在这一层)区分网络错误与协议错误。
100
+ */
101
+ function classifyXiaoEError(error) {
102
+ const taggedKind = readTag(error, 'errorKind', 'string');
103
+ const taggedStatus = readTag(error, 'status', 'number');
104
+ const taggedCause = readTag(error, 'causeCode', 'string');
105
+ const causeCode = taggedCause || readErrorCauseCode(error);
106
+ const withCause = causeCode ? { causeCode } : {};
107
+ const withStatus = taggedStatus === undefined ? {} : { status: taggedStatus };
108
+ if (taggedKind)
109
+ return { kind: taggedKind, ...withCause, ...withStatus };
110
+ const message = error instanceof Error ? error.message : String(error);
111
+ if (isAbortError(error))
112
+ return { kind: 'network', causeCode: causeCode || 'ETIMEDOUT' };
113
+ if (taggedStatus !== undefined) {
114
+ return { kind: classifyXiaoEHttpStatus(taggedStatus), ...withCause, status: taggedStatus };
115
+ }
116
+ if (causeCode && isNetworkErrorCode(causeCode))
117
+ return { kind: 'network', causeCode };
118
+ const httpMatch = /\bHTTP (\d{3})\b/u.exec(message);
119
+ if (httpMatch) {
120
+ const status = Number(httpMatch[1]);
121
+ return { kind: classifyXiaoEHttpStatus(status), ...withCause, status };
122
+ }
123
+ if (looksLikeTransportMessage(message))
124
+ return { kind: 'network', ...withCause };
125
+ return { kind: 'credential', ...withCause };
126
+ }
127
+ function noteXiaoEError(diagnostics, error, secrets = [], stage = 'e10_session') {
128
+ if (!diagnostics)
129
+ return;
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ const redacted = redactXiaoEDiagnostic(message, secrets);
132
+ if (redacted)
133
+ diagnostics.lastError = redacted;
134
+ const { kind, causeCode, status } = classifyXiaoEError(error);
135
+ const failure = {
136
+ stage: readTag(error, 'stage', 'string') ?? stage,
137
+ kind,
138
+ message: redacted,
139
+ ...(causeCode ? { causeCode } : {}),
140
+ ...(status === undefined ? {} : { httpStatus: status }),
141
+ };
142
+ if (!diagnostics.failures)
143
+ diagnostics.failures = [];
144
+ diagnostics.failures.push(failure);
145
+ }
146
+ /**
147
+ * 一次恢复失败的整体归因:只要出现过明确的凭证拒绝,就按凭证问题报(用户该去
148
+ * 重新登录);否则 HTTP 异常优先;只有**所有**失败都是网络错误时,才判定为
149
+ * 「暂时连不上 E10」,避免把网络抖动说成登录失效。
150
+ */
151
+ export function summarizeXiaoEFailureKind(diagnostics) {
152
+ const failures = diagnostics?.failures || [];
153
+ if (!failures.length)
154
+ return undefined;
155
+ if (failures.some((failure) => failure.kind === 'credential'))
156
+ return 'credential';
157
+ if (failures.some((failure) => failure.kind === 'http'))
158
+ return 'http';
159
+ return failures.every((failure) => failure.kind === 'network') ? 'network' : undefined;
160
+ }
161
+ /** 最后一条失败记录,用于把结构化错误的 `stage` 指向真正卡住的那一步。 */
162
+ export function lastXiaoEFailure(diagnostics) {
163
+ const failures = diagnostics?.failures || [];
164
+ return failures.length ? failures[failures.length - 1] : undefined;
165
+ }
60
166
  function envString(env, ...keys) {
61
167
  for (const key of keys) {
62
168
  const value = env[key];
@@ -89,6 +195,18 @@ export function hasXiaoEEnv(env = process.env, options = {}) {
89
195
  const explicitAgentType = envString(env, 'WEAVER_AGENT_TYPE');
90
196
  return explicitAgentType ? isXiaoERuntimeAgent(explicitAgentType) : false;
91
197
  }
198
+ export function resolveXiaoEClientType(env = process.env) {
199
+ const raw = envString(env, 'XIAOE_CLIENT_TYPE').toLowerCase();
200
+ if (raw === 'client' || raw === 'desktop' || raw === 'pc')
201
+ return 'client';
202
+ if (raw === 'cloud' || raw === 'web' || raw === 'browser')
203
+ return 'cloud';
204
+ return undefined;
205
+ }
206
+ /** 网页端运行时没有桌面凭据通道,只有平台票据可换。 */
207
+ export function isXiaoECloudRuntime(env = process.env) {
208
+ return resolveXiaoEClientType(env) === 'cloud';
209
+ }
92
210
  export function describeXiaoECredentialSource(source) {
93
211
  // Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
94
212
  // it is available in every sandbox run and needs no local desktop client.
@@ -134,6 +252,14 @@ function readString(data, ...keys) {
134
252
  }
135
253
  return '';
136
254
  }
255
+ function readExpiryTimes(data) {
256
+ for (const key of ['expiryTimes', 'expiryTime', 'expiresAt', 'expires_at']) {
257
+ const value = Number(data[key]);
258
+ if (Number.isFinite(value) && value > 0)
259
+ return Math.trunc(value);
260
+ }
261
+ return undefined;
262
+ }
137
263
  function cookieString(value) {
138
264
  if (!value)
139
265
  return '';
@@ -209,7 +335,19 @@ function readXiaoECredentialRecord(value, source, fallbackBaseUrl = '') {
209
335
  || cookieValue(rawCookie, 'ETEAMSID');
210
336
  if (!baseUrl || !eteamsId)
211
337
  return null;
212
- return { baseUrl: normalizeUrl(baseUrl), eteamsId, source, rawCookie };
338
+ const expiryTimes = candidates.map(readExpiryTimes).find((value) => value !== undefined);
339
+ return {
340
+ baseUrl: normalizeUrl(baseUrl),
341
+ eteamsId,
342
+ source,
343
+ rawCookie,
344
+ ...(expiryTimes !== undefined ? { expiryTimes } : {}),
345
+ };
346
+ }
347
+ function withDefaultXiaoEExpiry(credential) {
348
+ if (!credential || credential.expiryTimes !== undefined)
349
+ return credential;
350
+ return { ...credential, expiryTimes: Date.now() + XIAOE_ETEAMID_TTL_MS };
213
351
  }
214
352
  function parseXiaoEUnifiedLoginMap(raw, source) {
215
353
  const value = raw.trim();
@@ -533,7 +671,7 @@ async function resolveXiaoETokenScriptCredential(baseUrl, env = process.env) {
533
671
  const tokenData = await runXiaoETokenScript(baseUrl, env);
534
672
  if (!tokenData)
535
673
  return null;
536
- return readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl);
674
+ return withDefaultXiaoEExpiry(readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl));
537
675
  }
538
676
  async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env) {
539
677
  const scriptPath = xiaoEBundledEnvScriptPath();
@@ -542,7 +680,7 @@ async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env
542
680
  const tokenData = await runXiaoEPythonCredentialScript(scriptPath, baseUrl, env);
543
681
  if (!tokenData)
544
682
  return null;
545
- return readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl);
683
+ return withDefaultXiaoEExpiry(readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl));
546
684
  }
547
685
  export function xiaoEClientRobotsRoot(env = process.env) {
548
686
  if (process.platform === 'win32') {
@@ -684,15 +822,22 @@ export function inspectXiaoEUnifiedLoginSources(options = {}) {
684
822
  });
685
823
  const platformCredential = hasXiaoEPlatformCredential(env);
686
824
  const allowLocalDiscovery = options.allowLocalDiscovery !== false;
687
- const tokenScript = options.allowTokenScript !== false
825
+ const clientType = resolveXiaoEClientType(env);
826
+ // 网页端/沙箱不存在桌面凭据通道,跳过探测也就不会被机器上别人的
827
+ // 客户端登录态误命中。
828
+ const allowDesktopChannels = clientType !== 'cloud';
829
+ const tokenScript = allowDesktopChannels
830
+ && options.allowTokenScript !== false
688
831
  && hasXiaoETokenScript(env, allowLocalDiscovery);
689
- const clientCredential = hasXiaoEClientCredential(env, allowLocalDiscovery);
832
+ const clientCredential = allowDesktopChannels
833
+ && hasXiaoEClientCredential(env, allowLocalDiscovery);
690
834
  const environmentCredential = Boolean(baseUrl && resolveXiaoEUnifiedLoginCredential(baseUrl, env));
691
835
  return {
692
836
  platformCredential,
693
837
  tokenScript,
694
838
  clientCredential,
695
839
  environmentCredential,
840
+ clientType,
696
841
  any: platformCredential || tokenScript || clientCredential || environmentCredential,
697
842
  };
698
843
  }
@@ -719,11 +864,16 @@ async function fetchXiaoEClientEteamId(credential, domain) {
719
864
  if (resp.status < 300 || resp.status >= 400)
720
865
  break;
721
866
  if (redirects === 5) {
722
- throw new CliError('XiaoE client token endpoint redirected too many times.', ExitCode.AuthError);
867
+ throw tagXiaoEError('XiaoE client token endpoint redirected too many times.', {
868
+ errorKind: 'http',
869
+ });
723
870
  }
724
871
  const location = resp.headers.get('location');
725
872
  if (!location) {
726
- throw new CliError('XiaoE client token endpoint returned a redirect without Location.', ExitCode.AuthError);
873
+ throw tagXiaoEError('XiaoE client token endpoint returned a redirect without Location.', {
874
+ errorKind: 'http',
875
+ status: resp.status,
876
+ });
727
877
  }
728
878
  const next = new URL(location, endpoint);
729
879
  const sameOrigin = next.origin === endpoint.origin;
@@ -732,17 +882,32 @@ async function fetchXiaoEClientEteamId(credential, domain) {
732
882
  && next.hostname === endpoint.hostname;
733
883
  const safeProtocol = next.protocol === 'http:' || next.protocol === 'https:';
734
884
  if (!safeProtocol || next.username || next.password || (!sameOrigin && !sameHostHttpsUpgrade)) {
735
- throw new CliError('XiaoE client token endpoint attempted an unsafe cross-origin redirect.', ExitCode.AuthError);
885
+ throw tagXiaoEError('XiaoE client token endpoint attempted an unsafe cross-origin redirect.', {
886
+ errorKind: 'http',
887
+ status: resp.status,
888
+ });
736
889
  }
737
890
  await resp.body?.cancel().catch(() => undefined);
738
891
  endpoint = next;
739
892
  }
740
893
  if (!resp?.ok) {
741
- throw new CliError(`XiaoE client token endpoint returned HTTP ${resp?.status || 0}.`, ExitCode.AuthError);
894
+ const status = resp?.status || 0;
895
+ throw tagXiaoEError(`XiaoE client token endpoint returned HTTP ${status}.`, {
896
+ errorKind: classifyXiaoEHttpStatus(status),
897
+ status,
898
+ });
742
899
  }
743
- const data = await resp.json().catch(() => null);
900
+ const data = await resp.json().catch((error) => {
901
+ // 读响应体时被超时打断属于网络错误,不能当成「返回了非法 JSON」。
902
+ if (isAbortError(error))
903
+ throw error;
904
+ return null;
905
+ });
744
906
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
745
- throw new CliError('XiaoE client token endpoint returned invalid JSON.', ExitCode.AuthError);
907
+ throw tagXiaoEError('XiaoE client token endpoint returned invalid JSON.', {
908
+ errorKind: 'http',
909
+ status: resp.status,
910
+ });
746
911
  }
747
912
  const payload = data;
748
913
  const businessCode = payload.code;
@@ -751,10 +916,23 @@ async function fetchXiaoEClientEteamId(credential, domain) {
751
916
  : {};
752
917
  if ((businessCode !== '0000' && businessCode !== 0 && businessCode !== 200) || !readString(businessData, 'eteamId')) {
753
918
  const codeText = businessCode === undefined ? 'missing' : String(businessCode);
754
- throw new CliError(`XiaoE client token exchange failed with business code ${codeText}.`, ExitCode.AuthError);
919
+ throw tagXiaoEError(`XiaoE client token exchange failed with business code ${codeText}.`, {
920
+ errorKind: 'credential',
921
+ });
755
922
  }
756
923
  return payload;
757
924
  }
925
+ catch (error) {
926
+ if (isAbortError(error)) {
927
+ throw tagXiaoEError(`XiaoE client token endpoint timed out after ${XIAOE_CLIENT_TOKEN_TIMEOUT_MS} ms.`, { errorKind: 'network', causeCode: 'ETIMEDOUT' });
928
+ }
929
+ // 已经带语义的 HTTP / 业务码错误原样上抛,别再包一层网络错误。
930
+ if (error instanceof CliError)
931
+ throw error;
932
+ const causeCode = readErrorCauseCode(error);
933
+ const message = error instanceof Error ? error.message : String(error);
934
+ throw tagXiaoEError(`XiaoE client token endpoint request failed${causeCode ? ` (${causeCode})` : ''}: ${message}`, { errorKind: 'network', ...(causeCode ? { causeCode } : {}) });
935
+ }
758
936
  finally {
759
937
  clearTimeout(timer);
760
938
  }
@@ -766,7 +944,7 @@ async function resolveXiaoETokenCredential(credential, baseUrl) {
766
944
  // from the same origin as the token exchange. Newer 0000/200 responses keep
767
945
  // the XiaoE service origin separate and use the requested E10 target.
768
946
  const resolvedBaseUrl = payload.code === 0 ? credential.erpaUrl : targetBaseUrl;
769
- return readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl);
947
+ return withDefaultXiaoEExpiry(readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl));
770
948
  }
771
949
  async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
772
950
  const credential = readXiaoEPlatformCredential(env);
@@ -809,26 +987,62 @@ export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
809
987
  });
810
988
  }
811
989
  async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = '') {
812
- const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
813
- method: 'POST',
814
- headers: {
815
- accept: 'application/json',
816
- 'content-type': 'application/json',
817
- eteamsid: eteamsId,
818
- cookie: buildE10CookieHeader(rawCookie, eteamsId, agentType),
819
- 'user-agent': agentType ? `AgentType=${agentType},IsAgent=true` : 'weaver-work-cli',
820
- origin: baseUrl,
821
- },
822
- redirect: 'manual',
823
- });
824
- const data = await resp.json().catch(() => ({}));
825
- return { userInfo: readTeamsCheckUserInfo(resp, data), setCookie: extractRawSetCookie(resp) };
990
+ // teamsCheck 是恢复流程的必经往返:没有超时的话,服务端半开连接会把整条命令挂死。
991
+ const { signal, clear } = createTimeoutSignal(E10_TEAMS_CHECK_TIMEOUT_MS);
992
+ try {
993
+ const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
994
+ method: 'POST',
995
+ headers: {
996
+ accept: 'application/json',
997
+ 'content-type': 'application/json',
998
+ eteamsid: eteamsId,
999
+ cookie: buildE10CookieHeader(rawCookie, eteamsId, agentType),
1000
+ 'user-agent': agentType ? `AgentType=${agentType},IsAgent=true` : 'weaver-work-cli',
1001
+ origin: baseUrl,
1002
+ },
1003
+ redirect: 'manual',
1004
+ signal,
1005
+ });
1006
+ // 读响应体同样在超时窗口内;中途被 abort 不能当成「空响应」吞掉。
1007
+ const data = await resp.json().catch((error) => {
1008
+ if (isAbortError(error))
1009
+ throw error;
1010
+ return {};
1011
+ });
1012
+ return { userInfo: readTeamsCheckUserInfo(resp, data), setCookie: extractRawSetCookie(resp), status: resp.status };
1013
+ }
1014
+ catch (error) {
1015
+ if (isAbortError(error)) {
1016
+ throw tagXiaoEError(`E10 teamsCheck timed out after ${E10_TEAMS_CHECK_TIMEOUT_MS} ms.`, { errorKind: 'network', stage: 'teams_check', causeCode: 'ETIMEDOUT' });
1017
+ }
1018
+ if (error instanceof CliError)
1019
+ throw error;
1020
+ const causeCode = readErrorCauseCode(error);
1021
+ const message = error instanceof Error ? error.message : String(error);
1022
+ throw tagXiaoEError(`E10 teamsCheck request failed${causeCode ? ` (${causeCode})` : ''}: ${message}`, { errorKind: 'network', stage: 'teams_check', ...(causeCode ? { causeCode } : {}) });
1023
+ }
1024
+ finally {
1025
+ clear();
1026
+ }
826
1027
  }
827
1028
  async function saveXiaoEUnifiedLogin(input) {
828
1029
  const baseUrl = normalizeUrl(input.baseUrl);
829
- const { userInfo, setCookie } = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
1030
+ const { userInfo, setCookie, status } = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
830
1031
  if (!userInfo.userId) {
831
- throw new CliError('Could not resolve userId. Check whether XiaoE unified login is valid for this E10 domain.', ExitCode.AuthError);
1032
+ // 401/403/3xx 是「票不被接受」,用户该去恢复登录;5xx 则是 E10 侧故障,
1033
+ // 报成凭证问题会把人引向无用的重新登录。
1034
+ const statusText = status || 0;
1035
+ throw classifyXiaoEHttpStatus(statusText) === 'http'
1036
+ ? tagXiaoEError(`E10 teamsCheck returned HTTP ${statusText}.`, {
1037
+ errorKind: 'http',
1038
+ stage: 'teams_check',
1039
+ status: statusText,
1040
+ })
1041
+ : tagXiaoEError('Could not resolve userId. Check whether XiaoE unified login is valid for this E10 domain.', {
1042
+ errorKind: 'credential',
1043
+ stage: 'teams_check',
1044
+ status: statusText,
1045
+ });
832
1046
  }
833
1047
  const tenantKey = userInfo.tenantKey || input.tenantKeyFallback || '';
834
1048
  const profile = input.profileHint || profileNameFromUrl(baseUrl);
@@ -850,6 +1064,7 @@ async function saveXiaoEUnifiedLogin(input) {
850
1064
  baseUrl,
851
1065
  passportUrl: baseUrl,
852
1066
  cookieSavedAt: Date.now(),
1067
+ ...(input.expiryTimes !== undefined ? { expiryTimes: input.expiryTimes } : {}),
853
1068
  loginSource: 'xiaoe-unified',
854
1069
  credentialSourceStep: input.credentialSource.credentialSourceStep,
855
1070
  credentialSourceType: input.credentialSource.credentialSourceType,
@@ -872,6 +1087,7 @@ async function saveXiaoEUnifiedLogin(input) {
872
1087
  agentType: XIAOE_AGENT_TYPE,
873
1088
  version: '',
874
1089
  loginSource: 'xiaoe-unified',
1090
+ ...(input.expiryTimes !== undefined ? { expiryTimes: input.expiryTimes } : {}),
875
1091
  ...input.credentialSource,
876
1092
  },
877
1093
  };
@@ -884,19 +1100,32 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
884
1100
  profileBaseUrl: options.profileBaseUrl,
885
1101
  env,
886
1102
  });
887
- const mayUseTokenScript = options.allowTokenScript === true;
888
- if (!baseUrl && !mayUseTokenScript)
1103
+ // 网页端没有桌面凭据通道:`XIAOE_CLIENT_TYPE=cloud` 时不尝试 tokens.py 与
1104
+ // 本地客户端会话,避免在共享机器上误用他人的客户端登录态。
1105
+ const mayUseTokenScript = options.allowTokenScript === true && !isXiaoECloudRuntime(env);
1106
+ const diagnostics = options.diagnostics;
1107
+ const platformCredentialDetected = hasXiaoEPlatformCredential(env);
1108
+ const secrets = [envString(env, 'XIAOE_USER_TOKEN')];
1109
+ if (diagnostics) {
1110
+ diagnostics.baseUrl = baseUrl;
1111
+ diagnostics.platformCredentialDetected = platformCredentialDetected;
1112
+ diagnostics.clientType = resolveXiaoEClientType(env);
1113
+ }
1114
+ if (!baseUrl && !mayUseTokenScript) {
1115
+ noteXiaoEStep(diagnostics, 'base_url:missing');
889
1116
  return null;
1117
+ }
890
1118
  let credential = null;
891
1119
  // Step 1 — platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN).
892
1120
  // Works in sandboxes: no desktop client, no Python, no developer-machine paths.
893
- if (hasXiaoEPlatformCredential(env)) {
1121
+ if (platformCredentialDetected) {
894
1122
  options.logger?.info('Trying XiaoE platform credential from XIAOE_USER_TOKEN...');
895
1123
  try {
896
1124
  credential = await resolveXiaoEPlatformCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
897
1125
  }
898
1126
  catch (error) {
899
1127
  const message = error instanceof Error ? error.message : String(error);
1128
+ noteXiaoEError(diagnostics, error, secrets, 'platform_token');
900
1129
  options.logger?.warn('XiaoE platform credential failed. ' + message);
901
1130
  }
902
1131
  if (!credential) {
@@ -905,25 +1134,38 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
905
1134
  : 'XiaoE platform credential did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
906
1135
  }
907
1136
  }
1137
+ noteXiaoEStep(diagnostics, credential ? 'platform_token:ok' : `platform_token:${platformCredentialDetected ? 'failed' : 'missing'}`);
908
1138
  // Step 1b — compatibility path used by the reference login package. The
909
1139
  // script is shipped with the CLI npm package (never with a Skill) and its
910
1140
  // stdout stays inside this process so credentials are not exposed to agents.
911
- if (!credential && options.allowBundledEnvScript && hasXiaoEPlatformCredential(env)) {
912
- const scriptPath = xiaoEBundledEnvScriptPath();
913
- if (!scriptPath) {
914
- options.logger?.warn('CLI-bundled xiaoe_env.py was not found; will try desktop XiaoE credential sources next.');
1141
+ if (!credential) {
1142
+ if (!options.allowBundledEnvScript) {
1143
+ noteXiaoEStep(diagnostics, 'bundled_env_script:disabled');
1144
+ }
1145
+ else if (!platformCredentialDetected) {
1146
+ // 脚本本身也只读 XIAOE_USER_TOKEN/XIAOE_DOMAIN,前置缺失时重跑没有意义。
1147
+ noteXiaoEStep(diagnostics, 'bundled_env_script:missing');
915
1148
  }
916
1149
  else {
917
- options.logger?.info('Trying CLI-bundled xiaoe_env.py compatibility credential...');
918
- try {
919
- credential = await resolveXiaoEBundledEnvScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
920
- }
921
- catch (error) {
922
- const message = error instanceof Error ? error.message : String(error);
923
- options.logger?.warn('CLI-bundled xiaoe_env.py failed. ' + message);
1150
+ const scriptPath = xiaoEBundledEnvScriptPath();
1151
+ if (!scriptPath) {
1152
+ noteXiaoEStep(diagnostics, 'bundled_env_script:missing');
1153
+ options.logger?.warn('CLI-bundled xiaoe_env.py was not found; will try desktop XiaoE credential sources next.');
924
1154
  }
925
- if (!credential) {
926
- options.logger?.warn('CLI-bundled xiaoe_env.py did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
1155
+ else {
1156
+ options.logger?.info('Trying CLI-bundled xiaoe_env.py compatibility credential...');
1157
+ try {
1158
+ credential = await resolveXiaoEBundledEnvScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
1159
+ }
1160
+ catch (error) {
1161
+ const message = error instanceof Error ? error.message : String(error);
1162
+ noteXiaoEError(diagnostics, error, secrets, 'bundled_env_script');
1163
+ options.logger?.warn('CLI-bundled xiaoe_env.py failed. ' + message);
1164
+ }
1165
+ if (!credential) {
1166
+ options.logger?.warn('CLI-bundled xiaoe_env.py did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
1167
+ }
1168
+ noteXiaoEStep(diagnostics, `bundled_env_script:${credential ? 'ok' : 'failed'}`);
927
1169
  }
928
1170
  }
929
1171
  }
@@ -931,6 +1173,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
931
1173
  if (!credential && mayUseTokenScript) {
932
1174
  const tokenScriptPath = xiaoETokenScriptPath(env);
933
1175
  if (!tokenScriptPath) {
1176
+ noteXiaoEStep(diagnostics, 'token_script:missing');
934
1177
  options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the local XiaoE client session next.');
935
1178
  }
936
1179
  else {
@@ -940,11 +1183,13 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
940
1183
  }
941
1184
  catch (error) {
942
1185
  const message = error instanceof Error ? error.message : String(error);
1186
+ noteXiaoEError(diagnostics, error, secrets, 'token_script');
943
1187
  options.logger?.warn('XiaoE token script failed. ' + message);
944
1188
  }
945
1189
  if (!credential) {
946
1190
  options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the local XiaoE client session next.');
947
1191
  }
1192
+ noteXiaoEStep(diagnostics, `token_script:${credential ? 'ok' : 'failed'}`);
948
1193
  }
949
1194
  // Step 3 — built-in Node equivalent of tokens.py: reads the local XiaoE
950
1195
  // client session and exchanges it for an eteamId, so Python availability
@@ -956,30 +1201,52 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
956
1201
  }
957
1202
  catch (error) {
958
1203
  const message = error instanceof Error ? error.message : String(error);
1204
+ noteXiaoEError(diagnostics, error, secrets, 'client_session');
959
1205
  options.logger?.warn('Built-in XiaoE client credential failed. ' + message);
960
1206
  }
961
1207
  if (!credential) {
962
1208
  options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try WEAVER_XIAOE_* credentials next.');
963
1209
  }
1210
+ noteXiaoEStep(diagnostics, `client_session:${credential ? 'ok' : 'failed'}`);
964
1211
  }
1212
+ else if (!credential) {
1213
+ noteXiaoEStep(diagnostics, 'client_session:missing');
1214
+ }
1215
+ }
1216
+ else if (!credential) {
1217
+ noteXiaoEStep(diagnostics, 'token_script:disabled');
1218
+ noteXiaoEStep(diagnostics, 'client_session:disabled');
965
1219
  }
966
1220
  if (!credential && baseUrl)
967
1221
  credential = resolveXiaoEUnifiedLoginCredential(baseUrl, env);
968
- if (!credential)
1222
+ if (!credential) {
1223
+ noteXiaoEStep(diagnostics, 'environment_map:empty');
969
1224
  return null;
1225
+ }
1226
+ noteXiaoEStep(diagnostics, `credential_source:${credential.source}`);
970
1227
  options.logger?.info('Detected XiaoE unified login for this E10 domain. Trying XiaoE auth...');
971
1228
  try {
972
- return await saveXiaoEUnifiedLogin({
1229
+ const login = await saveXiaoEUnifiedLogin({
973
1230
  baseUrl: credential.baseUrl || baseUrl || DEFAULT_E10_BASE_URL,
974
1231
  eteamsId: credential.eteamsId,
975
1232
  rawCookie: credential.rawCookie,
1233
+ expiryTimes: credential.expiryTimes,
976
1234
  tenantKeyFallback: options.tenantKeyFallback,
977
1235
  profileHint: options.profileHint,
978
1236
  credentialSource: describeXiaoECredentialSource(credential.source),
979
1237
  });
1238
+ noteXiaoEStep(diagnostics, 'e10_session:ok');
1239
+ if (diagnostics) {
1240
+ // 成功换到登录态:前面压着的失败不该再让调用方误判整次恢复失败。
1241
+ delete diagnostics.lastError;
1242
+ delete diagnostics.failures;
1243
+ }
1244
+ return login;
980
1245
  }
981
1246
  catch (error) {
982
1247
  const message = error instanceof Error ? error.message : String(error);
1248
+ noteXiaoEError(diagnostics, error, secrets, 'e10_session');
1249
+ noteXiaoEStep(diagnostics, 'e10_session:failed');
983
1250
  options.logger?.warn('XiaoE unified login failed. ' + message);
984
1251
  return null;
985
1252
  }