weaver-work-cli 0.1.33 → 0.1.38

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 (126) hide show
  1. package/dist/chunks/{archive-FVFY6GZN.js → archive-PDV724YK.js} +5 -4
  2. package/dist/chunks/{archive-oa-NW3OD4UV.js → archive-oa-4XE4MNXQ.js} +4 -3
  3. package/dist/chunks/{asset-7QJKMIDV.js → asset-PDL2QE2N.js} +8 -7
  4. package/dist/chunks/{attachment-content-OSAVA6K3.js → attachment-content-MDDGOE5E.js} +3 -3
  5. package/dist/chunks/{blog-I7SDPNW4.js → blog-UYR3EUXW.js} +5 -4
  6. package/dist/chunks/{caizhilian-O37ZIQNY.js → caizhilian-LG2X5NMF.js} +5 -4
  7. package/dist/chunks/{calendar-7KXE4ED3.js → calendar-FG4BAZHE.js} +4 -3
  8. package/dist/chunks/{chunk-Q6LWB5HX.js → chunk-6FTTSA45.js} +4 -3
  9. package/dist/chunks/{chunk-MQ2I5AA3.js → chunk-6LTFIFLM.js} +3 -2
  10. package/dist/chunks/{chunk-WRRJ4GG2.js → chunk-CORFRD7Q.js} +4 -3
  11. package/dist/chunks/{chunk-WY74UBJ6.js → chunk-DMV2TOMH.js} +2194 -240
  12. package/dist/chunks/{chunk-MEEFGWTM.js → chunk-ELYDWBZJ.js} +135 -10
  13. package/dist/chunks/{chunk-EEWPQE3O.js → chunk-FUWVHMVV.js} +7 -6
  14. package/dist/chunks/{chunk-HEL5XTYI.js → chunk-GGJFMNJM.js} +26 -4
  15. package/dist/chunks/{chunk-KAS45KNR.js → chunk-IZUDA353.js} +1 -1
  16. package/dist/chunks/{chunk-2FLWXFVS.js → chunk-J7USS3ED.js} +4 -4
  17. package/dist/chunks/{chunk-IHUYP6LO.js → chunk-JSMFPO43.js} +1 -1
  18. package/dist/chunks/{chunk-RBGOMK4Y.js → chunk-KKRL3REV.js} +5 -4
  19. package/dist/chunks/{chunk-OCU4RLHC.js → chunk-NI6GIPIM.js} +195 -36
  20. package/dist/chunks/{chunk-PRHJGPMA.js → chunk-NR6RKXPA.js} +2 -2
  21. package/dist/chunks/{chunk-D223PTXU.js → chunk-OJQUJZQF.js} +33 -7
  22. package/dist/chunks/{chunk-ILMBNVSG.js → chunk-UJJD4QWF.js} +4 -3
  23. package/dist/chunks/{chunk-6ATGWRZM.js → chunk-UZZBA6PV.js} +177 -14
  24. package/dist/chunks/{chunk-GFPXQIY2.js → chunk-VRYHGG6O.js} +2 -2
  25. package/dist/chunks/{chunk-NHA62QD7.js → chunk-XJY7G4DD.js} +3 -3
  26. package/dist/chunks/{chunk-VBCFFFGT.js → chunk-Z32KWY6G.js} +4 -1
  27. package/dist/chunks/{conflict-SFNEVJY5.js → conflict-BQZEQ4SP.js} +2 -2
  28. package/dist/chunks/contract-XIRAFTEV.js +9111 -0
  29. package/dist/chunks/{cowork-CKWYHXYB.js → cowork-BLG62BL2.js} +46 -26
  30. package/dist/chunks/{ebuilder-form-KOLJRVZD.js → ebuilder-form-NUKG5QW4.js} +3 -3
  31. package/dist/chunks/{ehr-WFOC3B4D.js → ehr-2STW5ILH.js} +174 -10
  32. package/dist/chunks/{esb-LJZTN663.js → esb-UUDQUNKY.js} +4 -4
  33. package/dist/chunks/{fna-F7QRIIZC.js → fna-6RCY4NU5.js} +15 -9
  34. package/dist/chunks/{host-VQ6JJXGQ.js → host-7RXEDMAH.js} +3 -3
  35. package/dist/chunks/{host-KXPTEWFS.js → host-EJZFROU3.js} +3 -3
  36. package/dist/chunks/{host-BCGUVEWX.js → host-FWDOSRH5.js} +3 -3
  37. package/dist/chunks/{host-DGE2TZD4.js → host-RHM3NPXV.js} +3 -3
  38. package/dist/chunks/{host-BQBRHVR3.js → host-UFF33TWJ.js} +3 -3
  39. package/dist/chunks/{hrm-NI6XAKW5.js → hrm-PKJ6ELSH.js} +3 -3
  40. package/dist/chunks/{im-7TRTF3KT.js → im-YJAXS7CO.js} +3 -3
  41. package/dist/chunks/{invoice-AOXWJKYT.js → invoice-HWVSXGX6.js} +3 -3
  42. package/dist/chunks/{jiuchuanhui-7NQWEZAW.js → jiuchuanhui-L7VKILZB.js} +4 -3
  43. package/dist/chunks/{lazy-dispatch-RNS4EE4S.js → lazy-dispatch-NMYEIHRH.js} +4 -4
  44. package/dist/chunks/{mail-VDHJAB4O.js → mail-B42RRU7G.js} +46 -24
  45. package/dist/chunks/{meeting-GA7XCNNP.js → meeting-CFYVBJSS.js} +77 -12
  46. package/dist/chunks/{okr-TQIRERPQ.js → okr-GCLF6V7W.js} +4 -3
  47. package/dist/chunks/{plan-4RY4LN2P.js → plan-ZVL7GBRI.js} +4 -3
  48. package/dist/chunks/{project-D6XT2OC5.js → project-4YWAAE2K.js} +4 -3
  49. package/dist/chunks/{registry-QKMI43Q6.js → registry-EP4VTHZS.js} +8 -8
  50. package/dist/chunks/{registry-JHRIDQ3A.js → registry-FRJ6H7BT.js} +4 -4
  51. package/dist/chunks/{registry-VOGKFFTR.js → registry-SG2PGVCF.js} +2 -2
  52. package/dist/chunks/{room-recommend-D4EUOOWY.js → room-recommend-R3JVUUAC.js} +4 -4
  53. package/dist/chunks/{schedule-XPFBHPCV.js → schedule-NIJBTYDO.js} +4 -4
  54. package/dist/chunks/{workflow-HLBZRWIY.js → workflow-BB24DFBY.js} +11 -4
  55. package/dist/index.js +31 -26
  56. package/dist/internal/e10/auth-recovery.js +114 -1
  57. package/dist/internal/e10/client.js +16 -2
  58. package/dist/internal/e10/fetch-timing.js +31 -0
  59. package/dist/internal/e10/request-runtime.js +108 -7
  60. package/dist/shortcuts/archive/host.js +3 -2
  61. package/dist/shortcuts/archive-oa/host.js +2 -1
  62. package/dist/shortcuts/asset/host.js +6 -5
  63. package/dist/shortcuts/blog/host.js +3 -2
  64. package/dist/shortcuts/caizhilian/host.js +3 -2
  65. package/dist/shortcuts/calendar/host.js +2 -1
  66. package/dist/shortcuts/contract/host.js +2 -1
  67. package/dist/shortcuts/cowork/host.js +10 -1
  68. package/dist/shortcuts/cowork/operations/types.js +17 -2
  69. package/dist/shortcuts/ebuilder-form/host.js +3 -1
  70. package/dist/shortcuts/ehr/host.js +2 -1
  71. package/dist/shortcuts/ehr/operations/recruit-write.js +35 -0
  72. package/dist/shortcuts/ehr/operations/recruit.js +147 -0
  73. package/dist/shortcuts/esb/host.js +2 -1
  74. package/dist/shortcuts/fna/host.js +2 -1
  75. package/dist/shortcuts/fna/operations/expense-chart.js +19 -7
  76. package/dist/shortcuts/hrm/host.js +2 -1
  77. package/dist/shortcuts/im/host.js +3 -2
  78. package/dist/shortcuts/index.js +5 -0
  79. package/dist/shortcuts/invoice/host.js +5 -4
  80. package/dist/shortcuts/jiuchuanhui/host.js +2 -1
  81. package/dist/shortcuts/mail/host.js +10 -1
  82. package/dist/shortcuts/mail/operations/types.js +17 -2
  83. package/dist/shortcuts/meeting/host.js +3 -2
  84. package/dist/shortcuts/meeting/index.js +79 -1
  85. package/dist/shortcuts/meeting/invitation/text.js +71 -0
  86. package/dist/shortcuts/meeting/manifest.js +10 -0
  87. package/dist/shortcuts/meeting/operations/eb/catalog.js +5 -0
  88. package/dist/shortcuts/meeting/operations/eb/conflict.js +6 -1
  89. package/dist/shortcuts/meeting/operations/eb/create-flow.js +2105 -169
  90. package/dist/shortcuts/meeting/operations/eb/crm-search.js +478 -0
  91. package/dist/shortcuts/meeting/operations/eb/factory.js +161 -8
  92. package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
  93. package/dist/shortcuts/meeting/operations/eb/room-recommend.js +36 -2
  94. package/dist/shortcuts/meeting/operations/eb-fallback.js +4 -0
  95. package/dist/shortcuts/meeting/operations/env.js +6 -0
  96. package/dist/shortcuts/meeting/operations/invitation.js +200 -28
  97. package/dist/shortcuts/okr/host.js +2 -1
  98. package/dist/shortcuts/plan/host.js +2 -1
  99. package/dist/shortcuts/project/host.js +2 -1
  100. package/dist/shortcuts/workflow/endpoints.js +8 -3
  101. package/dist/shortcuts/workflow/operations/form.js +341 -36
  102. package/dist/shortcuts/workflow/operations.js +23 -1
  103. package/docs/ehr.md +1 -1
  104. package/docs/fna.md +2 -1
  105. package/package.json +1 -1
  106. package/skill-template/business-info.json +18 -0
  107. package/skill-template/domains/cowork.md +14 -0
  108. package/skills/weaver-e10-cowork/SKILL.md +101 -0
  109. package/skills/weaver-e10-cowork/references/cowork-read.md +90 -0
  110. package/skills/weaver-e10-cowork/references/cowork-write.md +131 -0
  111. package/skills/weaver-e10-cowork/references/safety-boundaries.md +45 -0
  112. package/skills/weaver-e10-jucailin/SKILL.md +21 -3
  113. package/skills/weaver-e10-jucailin/references/recruit-read.md +25 -0
  114. package/skills/weaver-e10-jucailin/references/recruit-write.md +13 -0
  115. package/skills/weaver-e10-jucailin/references/salary.md +15 -11
  116. package/skills/weaver-e10-mail/SKILL.md +12 -9
  117. package/skills/weaver-e10-meeting/SKILL.md +499 -300
  118. package/skills/weaver-e10-meeting/references/meeting-eb.md +334 -0
  119. package/skills/weaver-e10-meeting/references/meeting-invitation.md +64 -6
  120. package/skills/weaver-e10-meeting.zip +0 -0
  121. package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
  122. package/skills/weaver-e10-qiyecheng/SKILL.md +5 -2
  123. package/skills/weaver-e10-qiyecheng/references/catalog.md +2 -2
  124. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +15 -1
  125. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +22 -21
  126. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { CaizhilianCliError, caizhilianAuthError, caizhilianNetworkError, caizhilianValidationError, } from './errors.js';
6
7
  /** 业务请求默认超时(源 assets/config.json:http.timeout = 12 秒)。 */
7
8
  export const CAIZHILAN_TIMEOUT_MS = 12_000;
@@ -152,7 +153,7 @@ export function createCaizhilianHost(ctx, dependencies = {}) {
152
153
  }
153
154
  async function request(request) {
154
155
  const response = await doRequest(request, false).catch((error) => mapRuntimeNetworkError(error));
155
- if (response.status === 401 || response.status === 302) {
156
+ if (isE10SessionExpiredResponse(response)) {
156
157
  throw caizhilianAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
157
158
  }
158
159
  if (response.parseError) {
@@ -183,7 +184,7 @@ export function createCaizhilianHost(ctx, dependencies = {}) {
183
184
  }
184
185
  async function requestBinary(request) {
185
186
  const response = await doRequest(request, true).catch((error) => mapRuntimeNetworkError(error));
186
- if (response.status === 401 || response.status === 302) {
187
+ if (isE10SessionExpiredResponse(response)) {
187
188
  throw caizhilianAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
188
189
  }
189
190
  if (response.status < 200 || response.status >= 300) {
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { calendarAuthError, calendarNetworkError, CalendarCliError, calendarValidationError } from './errors.js';
6
7
  function inferProfile(ctx) {
7
8
  const options = ctx.getGlobalOptions();
@@ -110,7 +111,7 @@ export function createCalendarHost(ctx, dependencies = {}) {
110
111
  catch (error) {
111
112
  mapRuntimeNetworkError(error);
112
113
  }
113
- if (response.status === 401 || response.status === 302) {
114
+ if (isE10SessionExpiredResponse(response)) {
114
115
  throw calendarAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
115
116
  }
116
117
  if (response.parseError) {
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { basename } from 'node:path';
3
3
  import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
4
+ import { isE10SessionExpiredResponse, } from '../../internal/e10/auth-recovery.js';
4
5
  import { requireE10Session } from '../../internal/e10/context.js';
5
6
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
6
7
  import { E10ApiError, createE10RequestRuntime, } from '../../internal/e10/request-runtime.js';
@@ -179,7 +180,7 @@ export function createContractHost(ctx, dependencies = {}) {
179
180
  }
180
181
  throw error;
181
182
  }
182
- if (response.status === 401 || response.status === 302) {
183
+ if (isE10SessionExpiredResponse(response)) {
183
184
  throw contractAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status, path });
184
185
  }
185
186
  const { text, value } = response;
@@ -2,7 +2,9 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { CoworkCliError, coworkAuthError, coworkNetworkError, coworkValidationError } from './errors.js';
7
+ import { coworkPerf } from './operations/types.js';
6
8
  function inferProfile(ctx) {
7
9
  const options = ctx.getGlobalOptions();
8
10
  return options.profile || getActiveProfile();
@@ -97,7 +99,9 @@ export function createCoworkHost(ctx, dependencies = {}) {
97
99
  || ((innerCtx) => requireE10Session(innerCtx, { skipProfileEnrichment: true }));
98
100
  async function ensureSession() {
99
101
  if (!e10Session) {
102
+ const sessionStart = Date.now();
100
103
  e10Session = await requireSession(ctx);
104
+ coworkPerf.sessionMs = Date.now() - sessionStart;
101
105
  sessionContext = {
102
106
  baseUrl: e10Session.getBaseUrl(),
103
107
  profile: inferProfile(ctx),
@@ -123,6 +127,7 @@ export function createCoworkHost(ctx, dependencies = {}) {
123
127
  assertPath(path);
124
128
  const resolvedMethod = method || (body === undefined && form === undefined ? 'GET' : 'POST');
125
129
  let response;
130
+ const reqStart = Date.now();
126
131
  try {
127
132
  response = await (await runtime()).requestJson({
128
133
  module: 'cowork',
@@ -139,7 +144,11 @@ export function createCoworkHost(ctx, dependencies = {}) {
139
144
  catch (error) {
140
145
  mapRuntimeNetworkError(error);
141
146
  }
142
- if (response.status === 401 || response.status === 302) {
147
+ finally {
148
+ coworkPerf.requestCount += 1;
149
+ coworkPerf.requestMs += Date.now() - reqStart;
150
+ }
151
+ if (isE10SessionExpiredResponse(response)) {
143
152
  throw coworkAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
144
153
  }
145
154
  if (response.parseError) {
@@ -1,10 +1,25 @@
1
1
  export const READ = 'read';
2
2
  export const READ_BEFORE_WRITE = 'read-before-write';
3
3
  export const WRITE = 'write';
4
+ /** 进程内耗时收集器(周军锋 2026-10-20 要求:全链路耗时可量化)。host 写入、coworkSuccess 注出;内部诊断字段不进回答。 */
5
+ export const coworkPerf = {
6
+ t0: Date.now(),
7
+ sessionMs: 0,
8
+ requestCount: 0,
9
+ requestMs: 0,
10
+ };
4
11
  export function coworkSuccess(action, data, meta) {
5
12
  const payload = { ok: true, action, data };
6
- if (meta && Object.keys(meta).length > 0)
7
- payload.meta = meta;
13
+ const perf = {
14
+ elapsed_ms: Date.now() - coworkPerf.t0,
15
+ session_ms: coworkPerf.sessionMs || undefined,
16
+ requests: coworkPerf.requestCount || undefined,
17
+ network_ms: coworkPerf.requestMs || undefined,
18
+ };
19
+ for (const k of Object.keys(perf))
20
+ if (perf[k] === undefined)
21
+ delete perf[k];
22
+ payload.meta = { ...(meta || {}), ...perf };
8
23
  return payload;
9
24
  }
10
25
  /** 构造「对象类型 + 严格禁止额外字段」的 JSON Schema。 */
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
3
4
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
5
  import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
6
  import { ebuilderAuthError, ebuilderBusinessError, ebuilderNetworkError, ebuilderValidationError, } from './errors.js';
@@ -231,7 +232,8 @@ export function createEbuilderFormHost(ctx) {
231
232
  headers: options.headers,
232
233
  bigIntStrings: options.bigIntStrings ?? true,
233
234
  });
234
- if ([302, 401, 403].includes(response.status)) {
235
+ // 403 在 eBuilder 这里偏「无权」,3xx 只有跳到登录页才算登录失效(文件服务交接不算)。
236
+ if (isE10SessionExpiredResponse(response) || response.status === 403) {
235
237
  throw ebuilderAuthError('session_expired', 'E10 登录态已失效或当前账号无权访问该 eBuilder 数据', { httpStatus: response.status });
236
238
  }
237
239
  if (response.parseError) {
@@ -3,6 +3,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
3
3
  import { requireE10Session } from '../../internal/e10/context.js';
4
4
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
5
5
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
6
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
6
7
  import { ehrAuthError, ehrNetworkError, EhrCliError, ehrValidationError } from './errors.js';
7
8
  function inferProfile(ctx) {
8
9
  const options = ctx.getGlobalOptions();
@@ -165,7 +166,7 @@ export function createEhrHost(ctx, dependencies = {}) {
165
166
  catch (error) {
166
167
  mapRuntimeNetworkError(error);
167
168
  }
168
- if (response.status === 401 || response.status === 302) {
169
+ if (isE10SessionExpiredResponse(response)) {
169
170
  throw ehrAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
170
171
  }
171
172
  if (response.parseError) {
@@ -28,6 +28,31 @@ async function requireRecruitCache(env) {
28
28
  }
29
29
  return env.recruitContext.load(env.host);
30
30
  }
31
+ // ── 产物链接(2026-10-29 周军锋要求:写操作完成后自动生成链接返回对话框)──
32
+ // URL 模板与 recruit-stage 脚本同源:人才卡片 /sp/ebdpage/view/{ebdDetailPageId}?talentId=(实证);
33
+ // 列表滑出 /sp/ebdfpage/list/{listId}/card/0/{objId}/{dataId}/{listId}?id={dataId}&openSource=field&openSourceId={dataId}&openSourceAppId={appId}(面试管理实证,offer/entry 同构未单独实测);
34
+ // 列表直开 /sp/ebdfpage/list/{listId}[?talentId=](candidate.import 同构)。全部取自环境缓存,id 缺失返回 null 不编造。
35
+ function recruitTalentCardUrl(cache, baseUrl, talentId) {
36
+ if (!cache.ebdDetailPageId || !talentId)
37
+ return null;
38
+ const extra = String(cache.selfUse) === "1" ? "&recruitType=useSelf" : "";
39
+ return `${baseUrl.replace(/\/+$/, "")}/sp/ebdpage/view/${cache.ebdDetailPageId}?talentId=${talentId}${extra}`;
40
+ }
41
+ function recruitSlideLocateUrl(cache, baseUrl, listKey, formKey, dataId) {
42
+ const listId = cache.lists?.[listKey]?.listId;
43
+ const objId = cache.forms?.[formKey]?.objId;
44
+ if (!listId || !objId || !dataId || !cache.appId)
45
+ return null;
46
+ const base = baseUrl.replace(/\/+$/, "");
47
+ return `${base}/sp/ebdfpage/list/${listId}/card/0/${objId}/${dataId}/${listId}?id=${dataId}&openSource=field&openSourceId=${dataId}&openSourceAppId=${cache.appId}`;
48
+ }
49
+ function recruitOpenListUrl(cache, baseUrl, listKey, talentId) {
50
+ const menu = cache.lists?.[listKey];
51
+ if (!menu?.listId)
52
+ return null;
53
+ const extra = talentId ? `?talentId=${talentId}${String(cache.selfUse) === "1" ? "&recruitType=useSelf" : ""}` : "";
54
+ return `${baseUrl.replace(/\/+$/, "")}/sp/ebdfpage/list/${menu.listId}${extra}`;
55
+ }
31
56
  /** 建前查重:按手机号搜人才库(total 恒 0 平台缺陷,以 data.data[] 非空为准)。 */
32
57
  export async function checkDuplicateByMobile(env, cache, mobile) {
33
58
  const talent = cache.lists.talent;
@@ -501,6 +526,8 @@ const candidateCreateApply = {
501
526
  talentId: newId,
502
527
  initTalentBatch: initMsg,
503
528
  searchHint: `可用 ehr.recruit.candidate.search 按 name=${name} 或 mobile=${mobile} 回查`,
529
+ talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, newId),
530
+ talentListLocateUrl: recruitOpenListUrl(cache, lease.baseUrl, 'talent', newId),
504
531
  });
505
532
  },
506
533
  };
@@ -713,6 +740,8 @@ const interviewArrangeApply = {
713
740
  candidate: `${plan.candidateName}(talentId=${args.talentId})`,
714
741
  time: plan.interviewTime,
715
742
  interviewer: plan.interviewer,
743
+ talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, args.talentId),
744
+ interviewLocateUrl: recruitSlideLocateUrl(cache, lease.baseUrl, 'interview', 'interview_plan', planFormId),
716
745
  });
717
746
  },
718
747
  };
@@ -962,6 +991,8 @@ const offerSendApply = {
962
991
  sendOffer: 'SUCCESS',
963
992
  runEsb: { successNum: esbResult.successNum, failNum: esbResult.failNum, msg: esbResult.successMsg },
964
993
  nextStep: '回查 offer_status_select 应已变更为「已发送待反馈」',
994
+ talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, payload.talentId),
995
+ offerListUrl: recruitOpenListUrl(cache, lease.baseUrl, 'offer'),
965
996
  });
966
997
  },
967
998
  };
@@ -1241,6 +1272,7 @@ const entryProcessApply = {
1241
1272
  saveEstPreEntry: '已落账号',
1242
1273
  runEsb: { successNum: esbResult.successNum, failNum: esbResult.failNum, msg: esbResult.successMsg },
1243
1274
  nextStep: '回查入职列表 transact_status 应由 1 变为 2',
1275
+ entryListUrl: recruitOpenListUrl(cache, lease.baseUrl, 'entry'),
1244
1276
  });
1245
1277
  },
1246
1278
  };
@@ -1463,6 +1495,7 @@ const feedbackEditApply = {
1463
1495
  ? { result: backFeedback.result, comment: backFeedback.judgement, changed }
1464
1496
  : '回查未取到该反馈',
1465
1497
  ...(changed ? {} : { warning: '回查未确认变更生效,请人工在 E10 中核对面试反馈,禁止自动重试' }),
1498
+ talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, payload.talentId),
1466
1499
  });
1467
1500
  },
1468
1501
  };
@@ -1581,6 +1614,7 @@ const feedbackUrgeApply = {
1581
1614
  requireConfirm(args, 'ehr.recruit.candidate.interview.feedback.urge.apply');
1582
1615
  const lease = await verifyLease(args, 'ehr.recruit.candidate.interview.feedback.urge.apply', env);
1583
1616
  const payload = (lease.payload || {});
1617
+ const cache = await requireRecruitCache(env);
1584
1618
  if (!payload.feedbackRecordId || !payload.esbActionId || !payload.interviewDetailObjId) {
1585
1619
  throw ehrValidationError('continuation_payload_invalid', 'continuation 缺少催评参数,请重新执行 prepare');
1586
1620
  }
@@ -1602,6 +1636,7 @@ const feedbackUrgeApply = {
1602
1636
  status: 'COMPLETE',
1603
1637
  feedbackRecordId: payload.feedbackRecordId,
1604
1638
  runEsb: { successNum: esbResult.successNum, failNum: esbResult.failNum, msg: esbResult.successMsg },
1639
+ talentCardUrl: recruitTalentCardUrl(cache, lease.baseUrl, payload.talentId),
1605
1640
  });
1606
1641
  },
1607
1642
  };
@@ -698,6 +698,152 @@ const candidateList = {
698
698
  });
699
699
  },
700
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(';') } : {}),
844
+ });
845
+ },
846
+ };
701
847
  const candidateCard = {
702
848
  name: 'ehr.recruit.candidate.card',
703
849
  resource: 'recruit',
@@ -815,6 +961,7 @@ export const recruitOperations = [
815
961
  candidateSearch,
816
962
  candidateList,
817
963
  candidateCard,
964
+ interviewFeedbackTodo,
818
965
  ];
819
966
  export const RECRUIT_SECOND_PHASE_WITHHELD = [
820
967
  'recruit.candidate.create',
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { EsbCliError, esbApiError, esbAuthError, esbNetworkError, esbPermissionError, esbValidationError, esbWriteUncertain, } from './errors.js';
6
7
  /** 源接口文档给出的去域名路径;域名与认证由 E10 会话层托管。 */
7
8
  export const ESB_PATHS = {
@@ -117,7 +118,7 @@ export function createEsbHost(ctx, dependencies = {}) {
117
118
  catch (error) {
118
119
  mapRuntimeNetworkError(error);
119
120
  }
120
- if (response.status === 401 || response.status === 302) {
121
+ if (isE10SessionExpiredResponse(response)) {
121
122
  throw esbAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
122
123
  }
123
124
  if (response.parseError) {
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { FnaCliError, fnaAuthError, fnaNetworkError, fnaValidationError } from './errors.js';
6
7
  function inferProfile(ctx) {
7
8
  const options = ctx.getGlobalOptions();
@@ -116,7 +117,7 @@ export function createFnaHost(ctx, dependencies = {}) {
116
117
  catch (error) {
117
118
  mapRuntimeNetworkError(error);
118
119
  }
119
- if (response.status === 401 || response.status === 302) {
120
+ if (isE10SessionExpiredResponse(response)) {
120
121
  throw fnaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
121
122
  }
122
123
  if (response.parseError) {
@@ -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({
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { HrmCliError, hrmAuthError, hrmNetworkError, hrmValidationError } from './errors.js';
6
7
  function inferProfile(ctx) {
7
8
  const options = ctx.getGlobalOptions();
@@ -114,7 +115,7 @@ export function createHrmHost(ctx, dependencies = {}) {
114
115
  catch (error) {
115
116
  mapRuntimeNetworkError(error);
116
117
  }
117
- if (response.status === 401 || response.status === 302) {
118
+ if (isE10SessionExpiredResponse(response)) {
118
119
  throw hrmAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
119
120
  }
120
121
  if (response.parseError) {
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
4
4
  import { dirname, extname, resolve } from 'node:path';
5
5
  import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
6
+ import { isE10SessionExpiredResponse, } from '../../internal/e10/auth-recovery.js';
6
7
  import { requireE10Session } from '../../internal/e10/context.js';
7
8
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
8
9
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -121,7 +122,7 @@ export function createImHost(ctx, dependencies = {}) {
121
122
  catch (error) {
122
123
  mapRuntimeNetworkError(error);
123
124
  }
124
- if (response.status === 401 || response.status === 302) {
125
+ if (isE10SessionExpiredResponse(response)) {
125
126
  throw imAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
126
127
  }
127
128
  if (response.parseError) {
@@ -182,7 +183,7 @@ export function createImHost(ctx, dependencies = {}) {
182
183
  catch (error) {
183
184
  mapRuntimeNetworkError(error);
184
185
  }
185
- if (response.status === 401 || response.status === 302) {
186
+ if (isE10SessionExpiredResponse(response)) {
186
187
  throw imAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE);
187
188
  }
188
189
  if (response.status !== 200) {
@@ -130,6 +130,11 @@ export const builtinShortcuts = [
130
130
  description: '泛微E10采知连(文档中心)CLI',
131
131
  load: async () => (await import('./caizhilian/index.js')).caizhilianShortcut,
132
132
  }),
133
+ lazyShortcut({
134
+ name: 'contract',
135
+ description: '泛微E10今承达合同管理 CLI',
136
+ load: async () => (await import('./contract/index.js')).contractShortcut,
137
+ }),
133
138
  ];
134
139
  /** 全部 shortcut 的 CLI 元数据(不触发任何实现模块加载)。 */
135
140
  export function shortcutMetadata() {
@@ -4,6 +4,7 @@ import { E10_REAUTH_GUIDANCE, E10_SESSION_EXPIRED_MESSAGE } from '../../internal
4
4
  import { requireE10Session } from '../../internal/e10/context.js';
5
5
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
6
6
  import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
7
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
7
8
  import { InvoiceCliError, invoiceAuthError, invoiceNetworkError, invoiceValidationError, } from './errors.js';
8
9
  const MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
9
10
  const TOKEN_SERVICE = 'cn.weaver.weaver-work-cli.invoice-token';
@@ -199,7 +200,7 @@ export function createInvoiceHost(ctx, dependencies = {}) {
199
200
  timeoutMs,
200
201
  trustedHeaders: trustedInvoiceHeaders(token),
201
202
  });
202
- if (response.status === 401 || response.status === 302) {
203
+ if (isE10SessionExpiredResponse(response)) {
203
204
  throw invoiceAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
204
205
  }
205
206
  if (response.parseError) {
@@ -258,7 +259,7 @@ export function createInvoiceHost(ctx, dependencies = {}) {
258
259
  timeoutMs,
259
260
  trustedHeaders: trustedInvoiceHeaders(token),
260
261
  });
261
- if (response.status === 401 || response.status === 302) {
262
+ if (isE10SessionExpiredResponse(response)) {
262
263
  throw invoiceAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
263
264
  }
264
265
  if (response.parseError) {
@@ -293,7 +294,7 @@ export function createInvoiceHost(ctx, dependencies = {}) {
293
294
  timeoutMs,
294
295
  trustedHeaders: trustedInvoiceHeaders(token),
295
296
  });
296
- if (response.status === 401 || response.status === 302) {
297
+ if (isE10SessionExpiredResponse(response)) {
297
298
  throw invoiceAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
298
299
  }
299
300
  if (response.parseError) {
@@ -320,7 +321,7 @@ export function createInvoiceHost(ctx, dependencies = {}) {
320
321
  timeoutMs,
321
322
  trustedHeaders: trustedInvoiceHeaders(token),
322
323
  });
323
- if (response.status === 401 || response.status === 302) {
324
+ if (isE10SessionExpiredResponse(response)) {
324
325
  throw invoiceAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
325
326
  }
326
327
  const contentType = response.contentType;
@@ -2,6 +2,7 @@ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js
2
2
  import { requireE10Session } from '../../internal/e10/context.js';
3
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
5
+ import { isE10SessionExpiredResponse } from '../../internal/e10/auth-recovery.js';
5
6
  import { JiuchuanhuiCliError, jiuchuanhuiAuthError, jiuchuanhuiNetworkError, jiuchuanhuiValidationError, } from './errors.js';
6
7
  function inferProfile(ctx) {
7
8
  const options = ctx.getGlobalOptions();
@@ -127,7 +128,7 @@ export function createJiuchuanhuiHost(ctx, dependencies = {}) {
127
128
  catch (error) {
128
129
  mapRuntimeNetworkError(error);
129
130
  }
130
- if (response.status === 401 || response.status === 302) {
131
+ if (isE10SessionExpiredResponse(response)) {
131
132
  throw jiuchuanhuiAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
132
133
  }
133
134
  if (response.parseError) {