weaver-work-cli 0.1.26 → 0.1.29

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 (163) hide show
  1. package/README.md +7 -2
  2. package/dist/cmd/setup/index.js +1 -1
  3. package/dist/internal/e10/auth/commands.js +1 -6
  4. package/dist/internal/e10/auth/messages.js +3 -0
  5. package/dist/internal/e10/auth/session.js +8 -0
  6. package/dist/internal/e10/auth-recovery.js +148 -0
  7. package/dist/internal/e10/client.js +47 -15
  8. package/dist/internal/e10/context.js +8 -8
  9. package/dist/internal/e10/index.js +1 -0
  10. package/dist/internal/e10/request-runtime.js +51 -9
  11. package/dist/shortcuts/archive/host.js +3 -2
  12. package/dist/shortcuts/archive/manifest.js +1 -1
  13. package/dist/shortcuts/archive-oa/host.js +2 -1
  14. package/dist/shortcuts/archive-oa/manifest.js +1 -1
  15. package/dist/shortcuts/asset/host.js +7 -6
  16. package/dist/shortcuts/blog/host.js +4 -3
  17. package/dist/shortcuts/caizhilian/host.js +3 -2
  18. package/dist/shortcuts/caizhilian/manifest.js +1 -1
  19. package/dist/shortcuts/calendar/host.js +2 -1
  20. package/dist/shortcuts/contract/host.js +34 -59
  21. package/dist/shortcuts/ehr/host.js +2 -1
  22. package/dist/shortcuts/esb/host.js +2 -1
  23. package/dist/shortcuts/fna/host.js +6 -3
  24. package/dist/shortcuts/fna/manifest.js +10 -5
  25. package/dist/shortcuts/fna/operations/expense-chart.js +162 -0
  26. package/dist/shortcuts/fna/operations/registry.js +2 -0
  27. package/dist/shortcuts/hrm/host.js +2 -1
  28. package/dist/shortcuts/im/host.js +3 -2
  29. package/dist/shortcuts/invoice/host.js +9 -7
  30. package/dist/shortcuts/invoice/manifest.js +23 -142
  31. package/dist/shortcuts/invoice/operations/issuing-qys.js +266 -203
  32. package/dist/shortcuts/invoice/operations/registry.js +1 -6
  33. package/dist/shortcuts/jiuchuanhui/host.js +2 -1
  34. package/dist/shortcuts/mail/host.js +2 -1
  35. package/dist/shortcuts/meeting/host.js +2 -1
  36. package/dist/shortcuts/meeting/operations/eb/conflict.js +78 -12
  37. package/dist/shortcuts/okr/host.js +2 -1
  38. package/dist/shortcuts/plan/host.js +2 -1
  39. package/dist/shortcuts/project/host.js +2 -1
  40. package/dist/shortcuts/workflow/host.js +3 -5
  41. package/dist/shortcuts/workflow/index.js +4 -1
  42. package/dist/shortcuts/workflow/operations/chatscene.js +4 -22
  43. package/dist/shortcuts/workflow/operations/form.js +281 -166
  44. package/dist/shortcuts/workflow/operations/read.js +39 -8
  45. package/dist/shortcuts/workflow/operations/registry.js +2 -0
  46. package/dist/shortcuts/workflow/operations/todoSmartConvert/businessRules.js +331 -0
  47. package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +133 -0
  48. package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +326 -0
  49. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +113 -0
  50. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +259 -0
  51. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoListRows.js +64 -0
  52. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoOutputPlanner.js +28 -0
  53. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.aggregate.js +70 -0
  54. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +267 -0
  55. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +392 -0
  56. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +165 -0
  57. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +448 -0
  58. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +348 -0
  59. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +222 -0
  60. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +66 -0
  61. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +502 -0
  62. package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +540 -0
  63. package/dist/shortcuts/workflow/stdout-compact.js +7 -0
  64. package/docs/SKILL.md +1 -1
  65. package/docs/_catalog.md +2 -1
  66. package/docs/agent-invoice.md +1 -1
  67. package/docs/caizhilian.md +1 -1
  68. package/docs/e10-auth.md +1 -1
  69. package/docs/fna.md +7 -3
  70. package/docs/invoice.md +4 -6
  71. package/docs/todo-smart-convert/README.md +171 -0
  72. package/docs/todo-smart-convert/data-contract.md +236 -0
  73. package/docs/todo-smart-convert/mock/ai-todo-query-target.json +209 -0
  74. package/docs/todo-smart-convert/subgroup-rules.md +245 -0
  75. package/package.json +1 -1
  76. package/scripts/package-skill.mjs +1 -1
  77. package/skill-template/business-info.json +4 -1
  78. package/skill-template/domains/caizhilian.md +2 -1
  79. package/skill-template/domains/qiyecheng.md +1 -0
  80. package/skill-template/domains/yepiaotong.md +6 -2
  81. package/skill-template/skill-template.md +1 -0
  82. package/skills/weaver-e10-blog/SKILL.md +6 -1
  83. package/skills/weaver-e10-blog/references/blog-query.md +3 -1
  84. package/skills/weaver-e10-caizhilian/SKILL.md +18 -9
  85. package/skills/weaver-e10-caizhilian/references/action-flow.md +70 -0
  86. package/skills/weaver-e10-caizhilian/references/agent-entry.md +6 -2
  87. package/skills/weaver-e10-caizhilian/references/auth-and-install.md +207 -0
  88. package/skills/weaver-e10-caizhilian/references/file-collection.md +56 -54
  89. package/skills/weaver-e10-caizhilian/references/file-upload.md +4 -4
  90. package/skills/weaver-e10-caizhilian/references/host-executor-prompt.md +32 -0
  91. package/skills/weaver-e10-caizhilian/references/host-platform-integration.md +46 -0
  92. package/skills/weaver-e10-caizhilian/references/rule-index.md +56 -0
  93. package/skills/weaver-e10-caizhilian/references/setting-attach.md +5 -16
  94. package/skills/weaver-e10-caizhilian/references/setting-backup.md +4 -13
  95. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +6 -15
  96. package/skills/weaver-e10-caizhilian/references/setting-storage-docs-summary.md +126 -0
  97. package/skills/weaver-e10-caizhilian/references/setting-storage-field-map.md +57 -0
  98. package/skills/weaver-e10-caizhilian/references/setting-storage.md +142 -482
  99. package/skills/weaver-e10-caizhilian/references/source-manifest.json +7 -7
  100. package/skills/weaver-e10-caizhilian/references/task-workflow.md +87 -0
  101. package/skills/weaver-e10-caizhilian/references/withheld.md +2 -2
  102. package/skills/weaver-e10-caizhilian/references/write-protocol.md +7 -0
  103. package/skills/weaver-e10-calendar/SKILL.md +6 -1
  104. package/skills/weaver-e10-esb/SKILL.md +8 -0
  105. package/skills/weaver-e10-esb/references/input-format.md +3 -1
  106. package/skills/weaver-e10-esb/references/source-manifest.json +5 -5
  107. package/skills/weaver-e10-hrm/SKILL.md +6 -1
  108. package/skills/weaver-e10-hrm/references/employee.md +1 -1
  109. package/skills/weaver-e10-hrm/references/org.md +1 -1
  110. package/skills/weaver-e10-hrm/references/position.md +1 -1
  111. package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
  112. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +1 -1
  113. package/skills/weaver-e10-jiuchuanhui/SKILL.md +6 -1
  114. package/skills/weaver-e10-jucailin/SKILL.md +6 -1
  115. package/skills/weaver-e10-jucailin/references/hr-contact.md +1 -1
  116. package/skills/weaver-e10-jucailin/references/salary.md +1 -1
  117. package/skills/weaver-e10-mail/SKILL.md +6 -1
  118. package/skills/weaver-e10-meeting/SKILL.md +11 -6
  119. package/skills/weaver-e10-meeting/references/meeting-eb.md +7 -3
  120. package/skills/weaver-e10-meeting/references/meeting-queries.md +3 -1
  121. package/skills/weaver-e10-meeting/references/meeting-verification.md +4 -2
  122. package/skills/weaver-e10-okr/SKILL.md +6 -1
  123. package/skills/weaver-e10-okr/references/source-manifest.json +5 -5
  124. package/skills/weaver-e10-plan/SKILL.md +6 -1
  125. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +3 -1
  126. package/skills/weaver-e10-plan/references/plan-report-query.md +3 -1
  127. package/skills/weaver-e10-plan/references/source-manifest.json +7 -7
  128. package/skills/weaver-e10-qiyecheng/SKILL.md +16 -6
  129. package/skills/weaver-e10-qiyecheng/references/catalog.md +6 -3
  130. package/skills/weaver-e10-qiyecheng/references/expense-center.md +2 -0
  131. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +78 -0
  132. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +87 -33
  133. package/skills/weaver-e10-shared/SKILL.md +37 -14
  134. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +11 -8
  135. package/skills/weaver-e10-shared/references/non-interactive-environment.md +4 -4
  136. package/skills/weaver-e10-shared/references/weaver-e10-installation.md +3 -1
  137. package/skills/weaver-e10-wenshuding/SKILL.md +6 -1
  138. package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
  139. package/skills/weaver-e10-workflow/SKILL.md +23 -1
  140. package/skills/weaver-e10-workflow/references/source-manifest.json +1 -2
  141. package/skills/weaver-e10-workflow/references/todo-smart-convert.md +282 -0
  142. package/skills/weaver-e10-workflow/references/workflow-create.md +30 -55
  143. package/skills/weaver-e10-workflow/scripts/todo-summary.cjs +695 -0
  144. package/skills/weaver-e10-yepiaotong/SKILL.md +19 -17
  145. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +75 -78
  146. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +179 -0
  147. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +238 -0
  148. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
  149. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +11 -5
  150. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +55 -33
  151. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +102 -23
  152. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +1 -1
  153. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +53 -42
  154. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +94 -214
  155. package/skills/weaver-e10-yimiaoban/SKILL.md +9 -2
  156. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +1 -1
  157. package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -1
  158. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +9 -9
  159. package/skills/weaver-e10-ziguanjia/SKILL.md +6 -1
  160. package/dist/shortcuts/invoice/operations/issuing.js +0 -198
  161. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-field-rules.md +0 -161
  162. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-qys-ledger.md +0 -66
  163. package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +0 -107
package/README.md CHANGED
@@ -229,8 +229,13 @@ E10 配置/映射 → 当前 profile → `https://www.e-cology.com.cn`”解析
229
229
  `authentication/xiaoe_credential_unavailable`,不会自动打开浏览器或降级到
230
230
  `auth login`。确需浏览器授权时,调用方必须另行显式执行 `auth login`。
231
231
 
232
- 业务命令不会自动建立 XiaoE 登录态。桌面客户端需要 XiaoE 专属业务行为时,把全局参数
233
- 放在业务子命令之前,例如:
232
+ 业务命令不会在没有本地会话时主动建立 XiaoE 登录态,因此首次使用仍需显式执行
233
+ `auth xiaoe`。已有会话在接口层被明确判定为失效(HTTP 401/302、业务码 `-2` 或登录页)时,
234
+ 公共 E10 请求运行时会静默换取一次新会话并把被拒绝的请求重放一次;这个过程不向
235
+ stdout/stderr 输出中间“登录”步骤,并会合并同一 profile 的并发换票。流式请求、网络错误、
236
+ `partial` / `write_uncertain` 不会重放;恢复后的地址或用户身份变化时也会停止。
237
+
238
+ 桌面客户端需要 XiaoE 专属业务行为时,把全局参数放在业务子命令之前,例如:
234
239
 
235
240
  ```text
236
241
  weaver-work-cli --runtime-agent xiaoe workflow ...
@@ -69,7 +69,7 @@ export const setupCommand = {
69
69
  : null;
70
70
  const nextActions = [];
71
71
  if (!checks.find((check) => check.name === 'e10Auth')?.ok) {
72
- nextActions.push('Run: weaver-work-cli auth login --base-url <E10_BASE_URL> --agent_type <agent_type>');
72
+ nextActions.push('Authenticate using the method required by the current runtime, then rerun setup.');
73
73
  }
74
74
  if (!skillInstall) {
75
75
  nextActions.push('Optional Agent setup: weaver-work-cli skills install yepiaotong');
@@ -172,12 +172,7 @@ function cookieValue(rawCookie, name) {
172
172
  }
173
173
  function formatAuthStatus(status) {
174
174
  if (!status.loggedIn) {
175
- return [
176
- 'Not logged in.',
177
- 'In the XiaoE host, run: weaver-work-cli auth xiaoe',
178
- 'Otherwise: weaver-work-cli auth login --base-url <url> --agent_type <agent_type>',
179
- 'Or: weaver-work-cli auth set --eteamsid <id> --base-url <url> --agent_type <agent_type>',
180
- ].join('\n');
175
+ return 'Not logged in. Select the appropriate authentication method for the current runtime before retrying.';
181
176
  }
182
177
  const lines = [
183
178
  'Logged in.',
@@ -0,0 +1,3 @@
1
+ export const E10_REAUTH_GUIDANCE = '请结合当前宿主环境选择合适的登录方式,恢复登录后重试原请求。';
2
+ export const E10_AUTH_REQUIRED_MESSAGE = `E10 登录态不可用。${E10_REAUTH_GUIDANCE}`;
3
+ export const E10_SESSION_EXPIRED_MESSAGE = `E10 登录态已失效,本次操作已停止。${E10_REAUTH_GUIDANCE}`;
@@ -360,6 +360,14 @@ export class E10AuthSession {
360
360
  uaHeader() {
361
361
  return this.agentType ? `AgentType=${this.agentType},IsAgent=true` : 'weaver-work-cli';
362
362
  }
363
+ /**
364
+ * Adopt a freshly authenticated session in place. Hosts cache this object for
365
+ * the lifetime of one command, so mutating it keeps every subsequent request
366
+ * on the refreshed credential without exposing or re-reading auth data.
367
+ */
368
+ replaceWith(session) {
369
+ this.data = { ...session.data, cookies: { ...session.data.cookies } };
370
+ }
363
371
  toJSON() {
364
372
  const sourceInfo = {
365
373
  ...(this.loginSource ? { loginSource: this.loginSource } : {}),
@@ -0,0 +1,148 @@
1
+ import { getActiveProfile } from './auth/session.js';
2
+ import { hasXiaoEEnv, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
3
+ const AUTH_REQUIRED_CODES = new Set(['-2', '401']);
4
+ const AUTH_REQUIRED_MARKERS = [
5
+ '未登录',
6
+ '请登录',
7
+ '重新登录',
8
+ '登录失效',
9
+ '登录已失效',
10
+ '登录过期',
11
+ '登录已过期',
12
+ '登录超时',
13
+ '会话失效',
14
+ '会话过期',
15
+ '认证失败',
16
+ '身份认证失败',
17
+ 'token expired',
18
+ 'token timeout',
19
+ 'token invalid',
20
+ 'session expired',
21
+ 'login required',
22
+ 'not logged in',
23
+ 'not login',
24
+ ];
25
+ const recoveryInFlight = new Map();
26
+ function asRecord(value) {
27
+ return value && typeof value === 'object' && !Array.isArray(value)
28
+ ? value
29
+ : null;
30
+ }
31
+ function responseCodes(value) {
32
+ const root = asRecord(value);
33
+ if (!root)
34
+ return [];
35
+ const data = asRecord(root.data);
36
+ const resBase = asRecord(root.res_base);
37
+ const nestedResBase = asRecord(data?.res_base);
38
+ return [root.code, data?.code, resBase?.ret, nestedResBase?.ret]
39
+ .filter((item) => item !== undefined && item !== null)
40
+ .map(String);
41
+ }
42
+ function authenticationTextFields(value) {
43
+ if (typeof value === 'string')
44
+ return [value];
45
+ const root = asRecord(value);
46
+ if (!root)
47
+ return [];
48
+ const data = asRecord(root.data);
49
+ const values = [];
50
+ for (const record of [root, data]) {
51
+ if (!record)
52
+ continue;
53
+ for (const key of ['message', 'msg', 'error', 'errorMsg', 'rawText'])
54
+ values.push(record[key]);
55
+ }
56
+ return values
57
+ .filter((item) => typeof item === 'string' || typeof item === 'number')
58
+ .map(String);
59
+ }
60
+ function responseText(response) {
61
+ const fields = authenticationTextFields(response.value);
62
+ if (fields.length)
63
+ return fields.join('\n');
64
+ // A parsed object may contain ordinary user/business text such as "请登录".
65
+ // Never scan the complete serialized success payload for auth keywords.
66
+ if (asRecord(response.value))
67
+ return '';
68
+ if (typeof response.text === 'string' && response.text)
69
+ return response.text;
70
+ if (response.content && response.content.length) {
71
+ return response.content.subarray(0, 8 * 1024).toString('utf8');
72
+ }
73
+ return '';
74
+ }
75
+ /**
76
+ * Detect only explicit authentication rejection signals. A bare HTTP 403 is
77
+ * deliberately excluded because E10 also uses it for ordinary permission
78
+ * denials, which must never trigger a credential refresh.
79
+ */
80
+ export function isE10AuthenticationRequired(response) {
81
+ if (response.status === 401 || response.status === 302)
82
+ return true;
83
+ if (responseCodes(response.value).some((code) => AUTH_REQUIRED_CODES.has(code)))
84
+ return true;
85
+ const text = responseText(response).toLowerCase();
86
+ if (!text)
87
+ return false;
88
+ if (text.includes('login.jsp'))
89
+ return true;
90
+ if (text.includes('<input') && text.includes('password'))
91
+ return true;
92
+ return AUTH_REQUIRED_MARKERS.some((marker) => text.includes(marker.toLowerCase()));
93
+ }
94
+ function globalOptions(ctx) {
95
+ return typeof ctx.getGlobalOptions === 'function' ? ctx.getGlobalOptions() : {};
96
+ }
97
+ /** XiaoE automatic recovery is opt-in through explicit runtime evidence. */
98
+ export function canAutomaticallyRecoverXiaoE(ctx) {
99
+ const options = globalOptions(ctx);
100
+ // An explicitly supplied auth file is caller-owned and must not be replaced
101
+ // by a profile-backed XiaoE session behind the caller's back.
102
+ return !options.auth && hasXiaoEEnv(ctx.env || process.env, options);
103
+ }
104
+ /**
105
+ * Silently refresh a stale XiaoE session. Concurrent requests for the same
106
+ * profile/base URL share one credential exchange, so a burst of 401 responses
107
+ * does not start a burst of login attempts.
108
+ */
109
+ export async function recoverXiaoEAuthSession(ctx, currentSession) {
110
+ if (!canAutomaticallyRecoverXiaoE(ctx))
111
+ return null;
112
+ const options = globalOptions(ctx);
113
+ const profile = options.profile || getActiveProfile();
114
+ const baseUrl = currentSession.getBaseUrl();
115
+ const key = `${profile}\n${baseUrl}`;
116
+ const existing = recoveryInFlight.get(key);
117
+ if (existing)
118
+ return existing;
119
+ const recovery = (async () => {
120
+ const login = await tryXiaoEUnifiedLogin({
121
+ env: ctx.env,
122
+ baseUrl,
123
+ profileBaseUrl: baseUrl,
124
+ allowBundledEnvScript: true,
125
+ allowTokenScript: true,
126
+ tenantKeyFallback: currentSession.tenantKey,
127
+ profileHint: profile,
128
+ // Intentionally no logger: transparent recovery must not leak an
129
+ // intermediate "login" step into agent stdout/stderr or JSON output.
130
+ });
131
+ return login?.session || null;
132
+ })();
133
+ recoveryInFlight.set(key, recovery);
134
+ try {
135
+ return await recovery;
136
+ }
137
+ finally {
138
+ if (recoveryInFlight.get(key) === recovery)
139
+ recoveryInFlight.delete(key);
140
+ }
141
+ }
142
+ export function sameE10SessionIdentity(current, recovered) {
143
+ const currentBaseUrl = current.getBaseUrl().replace(/\/+$/u, '').toLowerCase();
144
+ const recoveredBaseUrl = recovered.getBaseUrl().replace(/\/+$/u, '').toLowerCase();
145
+ if (currentBaseUrl !== recoveredBaseUrl)
146
+ return false;
147
+ return !current.userId || !recovered.userId || current.userId === recovered.userId;
148
+ }
@@ -1,9 +1,14 @@
1
+ import { isE10AuthenticationRequired, sameE10SessionIdentity, } from './auth-recovery.js';
2
+ class E10AuthenticationContextChangedError extends Error {
3
+ }
1
4
  export class E10ApiClient {
2
5
  auth;
3
6
  retries;
4
- constructor(auth, retries = 3) {
7
+ recoverAuthentication;
8
+ constructor(auth, retries = 3, recoverAuthentication) {
5
9
  this.auth = auth;
6
10
  this.retries = retries;
11
+ this.recoverAuthentication = recoverAuthentication;
7
12
  }
8
13
  getBaseUrl() {
9
14
  return this.auth.getBaseUrl();
@@ -56,31 +61,51 @@ export class E10ApiClient {
56
61
  }
57
62
  async requestRaw(url, init) {
58
63
  let lastError = null;
64
+ let authenticationRecoveryAttempted = false;
59
65
  for (let attempt = 1; attempt <= this.retries; attempt++) {
60
66
  try {
61
67
  const resp = await fetch(url, { ...init, redirect: 'manual' });
68
+ const text = await resp.text();
69
+ let value = null;
70
+ if (text) {
71
+ try {
72
+ value = JSON.parse(text);
73
+ }
74
+ catch {
75
+ value = text;
76
+ }
77
+ }
78
+ if (!authenticationRecoveryAttempted && isE10AuthenticationRequired({
79
+ status: resp.status,
80
+ headers: resp.headers,
81
+ text,
82
+ value,
83
+ })) {
84
+ authenticationRecoveryAttempted = true;
85
+ const recovered = await this.recoverAuthentication?.(this.auth) || null;
86
+ if (recovered) {
87
+ if (!sameE10SessionIdentity(this.auth, recovered)) {
88
+ throw new E10AuthenticationContextChangedError('XiaoE automatic authentication recovery changed the E10 login context');
89
+ }
90
+ this.auth.replaceWith(recovered);
91
+ refreshAuthenticationHeaders(init, this.auth);
92
+ attempt -= 1; // the one authentication retry does not consume the transport retry budget
93
+ continue;
94
+ }
95
+ }
96
+ // Preserve the legacy password-based refresh for non-XiaoE callers.
62
97
  if (resp.status === 302) {
63
98
  const ok = await this.auth.ensureValid();
64
99
  if (!ok)
65
100
  throw new Error('E10 session expired; please login again');
66
- const headers = (init.headers || {});
67
- headers.eteamsid = this.auth.eteamsId;
68
- headers.cookie = this.auth.cookieHeader();
69
- headers['user-agent'] = this.auth.uaHeader();
70
- init.headers = headers;
101
+ refreshAuthenticationHeaders(init, this.auth);
71
102
  continue;
72
103
  }
73
- const text = await resp.text();
74
- if (!text)
75
- return null;
76
- try {
77
- return JSON.parse(text);
78
- }
79
- catch {
80
- return text;
81
- }
104
+ return value;
82
105
  }
83
106
  catch (error) {
107
+ if (error instanceof E10AuthenticationContextChangedError)
108
+ throw error;
84
109
  lastError = error instanceof Error ? error : new Error(String(error));
85
110
  if (attempt < this.retries) {
86
111
  const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
@@ -102,3 +127,10 @@ export class E10ApiClient {
102
127
  return { body, boundary };
103
128
  }
104
129
  }
130
+ function refreshAuthenticationHeaders(init, session) {
131
+ const headers = (init.headers || {});
132
+ headers.eteamsid = session.eteamsId;
133
+ headers.cookie = session.cookieHeader();
134
+ headers['user-agent'] = session.uaHeader();
135
+ init.headers = headers;
136
+ }
@@ -1,7 +1,8 @@
1
1
  import { CliError, ExitCode } from '../../core/errors.js';
2
+ import { E10_AUTH_REQUIRED_MESSAGE } from './auth/messages.js';
2
3
  import { E10AuthSession } from './auth/session.js';
3
- import { hasXiaoEEnv } from './auth/xiaoe.js';
4
4
  import { E10ApiClient } from './client.js';
5
+ import { recoverXiaoEAuthSession } from './auth-recovery.js';
5
6
  export async function getE10Session(ctx, load = {}) {
6
7
  const options = ctx.getGlobalOptions();
7
8
  return E10AuthSession.fromOptions(options, ctx.cwd, load);
@@ -9,18 +10,17 @@ export async function getE10Session(ctx, load = {}) {
9
10
  export async function requireE10Session(ctx, load = {}) {
10
11
  const session = await getE10Session(ctx, load);
11
12
  if (!session) {
12
- const options = ctx.getGlobalOptions();
13
- if (hasXiaoEEnv(ctx.env, options)) {
14
- throw new CliError('E10 auth not found. The current runtime is XiaoE; run weaver-work-cli auth xiaoe first.', ExitCode.AuthError);
15
- }
16
- throw new CliError('E10 auth not found. Ask the user for the E10 login domain (base URL), then run: weaver-work-cli auth login --base-url <url> --agent_type <agent_type>.', ExitCode.AuthError);
13
+ throw new CliError(E10_AUTH_REQUIRED_MESSAGE, ExitCode.AuthError);
17
14
  }
18
15
  return session;
19
16
  }
20
17
  export async function getE10Client(ctx) {
21
18
  const session = await getE10Session(ctx);
22
- return session ? new E10ApiClient(session) : null;
19
+ return session
20
+ ? new E10ApiClient(session, 3, (current) => recoverXiaoEAuthSession(ctx, current))
21
+ : null;
23
22
  }
24
23
  export async function requireE10Client(ctx) {
25
- return new E10ApiClient(await requireE10Session(ctx));
24
+ const session = await requireE10Session(ctx);
25
+ return new E10ApiClient(session, 3, (current) => recoverXiaoEAuthSession(ctx, current));
26
26
  }
@@ -1,3 +1,4 @@
1
1
  export { E10ApiClient } from './client.js';
2
2
  export { getE10Client, getE10Session, requireE10Client, requireE10Session } from './context.js';
3
3
  export { E10ApiError, E10RequestRuntime, createE10RequestRuntime } from './request-runtime.js';
4
+ export { canAutomaticallyRecoverXiaoE, isE10AuthenticationRequired, recoverXiaoEAuthSession, } from './auth-recovery.js';
@@ -1,4 +1,5 @@
1
1
  import { CliError, ExitCode } from '../../core/errors.js';
2
+ import { isE10AuthenticationRequired, recoverXiaoEAuthSession, sameE10SessionIdentity, } from './auth-recovery.js';
2
3
  const DEFAULT_TIMEOUT_MS = 30_000;
3
4
  const DEFAULT_BIG_INT_STRINGS = true;
4
5
  const RESERVED_HEADERS = [
@@ -29,11 +30,14 @@ export class E10RequestRuntime {
29
30
  interceptors;
30
31
  fetchFn;
31
32
  attachSessionTicket;
33
+ recoverAuthentication;
32
34
  constructor(ctx, session, dependencies = {}) {
33
35
  this.ctx = ctx;
34
36
  this.session = session;
35
37
  this.fetchFn = dependencies.fetch || globalThis.fetch;
36
38
  this.attachSessionTicket = dependencies.attachSessionTicket === true;
39
+ this.recoverAuthentication = dependencies.recoverAuthentication
40
+ || ((current) => recoverXiaoEAuthSession(this.ctx, current));
37
41
  this.interceptors = [
38
42
  ...(this.ctx.hooks?.getE10RequestInterceptors?.() || []),
39
43
  ...(dependencies.interceptors || []),
@@ -47,9 +51,8 @@ export class E10RequestRuntime {
47
51
  }
48
52
  async request(request) {
49
53
  const mutable = this.prepareRequest(request);
50
- const sessionContext = this.sessionContext();
51
54
  try {
52
- await this.runBeforeRequest(mutable, sessionContext);
55
+ await this.runBeforeRequest(mutable, this.sessionContext());
53
56
  this.assertPath(mutable.path);
54
57
  if (mutable.path !== request.path) {
55
58
  throw new E10ApiError('policy', 'path_rewrite_denied', 'E10 API 拦截器不能修改请求路径', {
@@ -58,19 +61,58 @@ export class E10RequestRuntime {
58
61
  });
59
62
  }
60
63
  stripReservedHeaders(mutable.headers);
61
- const url = withQuery(this.session.getBaseUrl(), mutable.path, mutable.query);
62
- const headers = this.composeHeaders(mutable, request.trustedHeaders);
63
- const init = this.buildFetchInit(mutable, headers);
64
- const response = await this.fetchWithTimeout(url, init, mutable.timeoutMs);
65
- const parsed = await this.readResponse(response, mutable.responseType, mutable.bigIntStrings, mutable.maxResponseBytes, mutable.timeoutMs);
66
- await this.runAfterResponse(mutable, sessionContext, parsed);
64
+ const requestEteamsId = this.session.eteamsId;
65
+ let parsed = await this.executeRequest(mutable, request.trustedHeaders);
66
+ if (!mutable.streamBody
67
+ && isE10AuthenticationRequired(parsed)
68
+ && await this.recoverExpiredSession(requestEteamsId)) {
69
+ // Authentication rejection is a definitive pre-business failure. Retry
70
+ // exactly once with the new session; never retry transport/partial errors.
71
+ parsed = await this.executeRequest(mutable, request.trustedHeaders);
72
+ }
73
+ await this.runAfterResponse(mutable, this.sessionContext(), parsed);
67
74
  return parsed;
68
75
  }
69
76
  catch (error) {
70
- await this.runOnError(mutable, sessionContext, error);
77
+ await this.runOnError(mutable, this.sessionContext(), error);
71
78
  throw error;
72
79
  }
73
80
  }
81
+ async executeRequest(request, trustedHeaders = {}) {
82
+ const url = withQuery(this.session.getBaseUrl(), request.path, request.query);
83
+ const headers = this.composeHeaders(request, trustedHeaders);
84
+ const init = this.buildFetchInit(request, headers);
85
+ const response = await this.fetchWithTimeout(url, init, request.timeoutMs);
86
+ return this.readResponse(response, request.responseType, request.bigIntStrings, request.maxResponseBytes, request.timeoutMs);
87
+ }
88
+ async recoverExpiredSession(requestEteamsId) {
89
+ // Another concurrent request may already have refreshed the shared session
90
+ // object while this response was in flight.
91
+ if (this.session.eteamsId && this.session.eteamsId !== requestEteamsId)
92
+ return true;
93
+ let recovered;
94
+ try {
95
+ recovered = await this.recoverAuthentication(this.session);
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ if (!recovered)
101
+ return false;
102
+ if (!sameE10SessionIdentity(this.session, recovered)) {
103
+ throw new E10ApiError('authentication', 'session_context_changed', 'XiaoE 自动恢复后的登录身份或 E10 环境发生变化,本次请求未重试', { exitCode: ExitCode.AuthError, retryable: false });
104
+ }
105
+ const original = this.session;
106
+ if (typeof original.replaceWith === 'function') {
107
+ original.replaceWith(recovered);
108
+ this.session = original;
109
+ }
110
+ else {
111
+ // Structural test doubles and older embedders may not expose replaceWith.
112
+ this.session = recovered;
113
+ }
114
+ return true;
115
+ }
74
116
  prepareRequest(request) {
75
117
  return {
76
118
  module: request.module,
@@ -1,3 +1,4 @@
1
+ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
1
2
  import { requireE10Session } from '../../internal/e10/context.js';
2
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
3
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -151,7 +152,7 @@ export function createArchiveHost(ctx, dependencies = {}) {
151
152
  async function request(request) {
152
153
  const response = await doRequest(request, false).catch((error) => mapRuntimeNetworkError(error));
153
154
  if (response.status === 401 || response.status === 302) {
154
- throw archiveAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> 完成登录', { httpStatus: response.status });
155
+ throw archiveAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
155
156
  }
156
157
  if (response.parseError) {
157
158
  if (response.status < 200 || response.status >= 300) {
@@ -182,7 +183,7 @@ export function createArchiveHost(ctx, dependencies = {}) {
182
183
  async function requestBinary(request) {
183
184
  const response = await doRequest(request, true).catch((error) => mapRuntimeNetworkError(error));
184
185
  if (response.status === 401 || response.status === 302) {
185
- throw archiveAuthError('session_expired', 'E10 登录态已失效,请先登录', { httpStatus: response.status });
186
+ throw archiveAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
186
187
  }
187
188
  if (response.status < 200 || response.status >= 300) {
188
189
  throw new ArchiveCliError('api', 'http_error', `档案下载 HTTP ${response.status}`, {
@@ -20,7 +20,7 @@ export const archiveManifest = {
20
20
  capabilities: ['fonds', 'prelib', 'search', 'borrowCar', 'borrowList', 'flow', 'download', 'upload'],
21
21
  auth: {
22
22
  required: 'e10-session',
23
- note: '所有操作都要求已登录 E10(weaver-work-cli auth login --base-url <域名>),未登录时返回 auth_error。',
23
+ note: '所有操作都要求已登录 E10;未登录时返回 auth_error,调用方应根据当前宿主环境选择登录方式。',
24
24
  },
25
25
  rules: [
26
26
  '写操作(*.apply)必须先执行同名的 prepare 拿到 continuation,再带 confirm=true 执行 apply;CLI 用 --yes 表示确认。',
@@ -1,3 +1,4 @@
1
+ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
1
2
  import { requireE10Session } from '../../internal/e10/context.js';
2
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
3
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -122,7 +123,7 @@ export function createArchiveOaHost(ctx, dependencies = {}) {
122
123
  timeoutMs,
123
124
  }).catch((error) => mapRuntimeNetworkError(error));
124
125
  if (response.status === 401 || response.status === 302) {
125
- throw archiveOaAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> 完成登录', { httpStatus: response.status });
126
+ throw archiveOaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
126
127
  }
127
128
  if (response.parseError) {
128
129
  if (response.status < 200 || response.status >= 300) {
@@ -20,7 +20,7 @@ export const archiveOaManifest = {
20
20
  capabilities: ['archiveLocate', 'contractSearch', 'borrowCar'],
21
21
  auth: {
22
22
  required: 'e10-session',
23
- note: '所有操作都要求已登录 E10(weaver-work-cli auth login --base-url <域名>)。发起借阅不在 CLI 能力范围内,由用户在浏览器登录后于界面自己完成。',
23
+ note: '所有操作都要求已登录 E10,具体登录方式由调用方根据当前宿主环境选择。发起借阅不在 CLI 能力范围内,由用户在浏览器登录后于界面自己完成。',
24
24
  },
25
25
  rules: [
26
26
  '写操作(*.apply)必须先执行同名的 prepare 拿到 continuation,再带 confirm=true 执行 apply;CLI 用 --yes 表示确认。',
@@ -1,3 +1,4 @@
1
+ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
1
2
  import { requireE10Session } from '../../internal/e10/context.js';
2
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
3
4
  import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -176,7 +177,7 @@ export function createZiguanjiaHost(ctx) {
176
177
  bigIntStrings: true,
177
178
  });
178
179
  if (response.status === 401 || response.status === 302) {
179
- throw ziguanjiaAuthError('session_expired', 'E10 登录态已失效,请先运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录后再试', { httpStatus: response.status });
180
+ throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
180
181
  }
181
182
  if (response.parseError) {
182
183
  throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
@@ -242,7 +243,7 @@ export function createZiguanjiaHost(ctx) {
242
243
  bigIntStrings: false,
243
244
  });
244
245
  if (response.status === 401 || response.status === 302) {
245
- throw ziguanjiaAuthError('session_expired', 'E10 登录态已失效,请先运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录后再试', { httpStatus: response.status });
246
+ throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
246
247
  }
247
248
  if (response.parseError) {
248
249
  throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
@@ -315,7 +316,7 @@ export function createZiguanjiaHost(ctx) {
315
316
  bigIntStrings: true,
316
317
  });
317
318
  if (response.status === 401 || response.status === 302) {
318
- throw ziguanjiaAuthError('session_expired', 'E10 登录态已失效,请先运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录后再试', { httpStatus: response.status });
319
+ throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
319
320
  }
320
321
  if (response.parseError) {
321
322
  throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
@@ -340,7 +341,7 @@ export function createZiguanjiaHost(ctx) {
340
341
  }
341
342
  function handleEbusiness(value) {
342
343
  if (isEbSessionExpired(value)) {
343
- throw ziguanjiaAuthError('session_expired', 'E10 登录态已失效,请先运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录后再试', { ret: ebCode(value) });
344
+ throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { ret: ebCode(value) });
344
345
  }
345
346
  const code = ebCode(value);
346
347
  if (!isEbSuccessCode(code)) {
@@ -363,7 +364,7 @@ export function createZiguanjiaHost(ctx) {
363
364
  bigIntStrings: true,
364
365
  });
365
366
  if (response.status === 401 || response.status === 302) {
366
- throw ziguanjiaAuthError('session_expired', 'E10 登录态已失效,请先运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录后再试', { httpStatus: response.status });
367
+ throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
367
368
  }
368
369
  if (response.parseError) {
369
370
  throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
@@ -393,7 +394,7 @@ export function createZiguanjiaHost(ctx) {
393
394
  bigIntStrings: true,
394
395
  });
395
396
  if (response.status === 401 || response.status === 302) {
396
- throw ziguanjiaAuthError('session_expired', 'E10 登录态已失效,请先运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录后再试', { httpStatus: response.status });
397
+ throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
397
398
  }
398
399
  if (response.parseError) {
399
400
  throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
@@ -1,4 +1,5 @@
1
1
  import { ExitCode } from '../../core/errors.js';
2
+ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
2
3
  import { requireE10Session } from '../../internal/e10/context.js';
3
4
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
4
5
  import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -75,7 +76,7 @@ export function createBlogHost(ctx, dependencies = {}) {
75
76
  }
76
77
  function assertSession(value) {
77
78
  if (isSessionExpired(value)) {
78
- throw blogAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录');
79
+ throw blogAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE);
79
80
  }
80
81
  }
81
82
  async function blogRequest(request) {
@@ -91,7 +92,7 @@ export function createBlogHost(ctx, dependencies = {}) {
91
92
  timeoutMs,
92
93
  });
93
94
  if (response.status === 401 || response.status === 302) {
94
- throw blogAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录', { httpStatus: response.status });
95
+ throw blogAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
95
96
  }
96
97
  if (response.parseError) {
97
98
  if (response.status < 200 || response.status >= 300) {
@@ -134,7 +135,7 @@ export function createBlogHost(ctx, dependencies = {}) {
134
135
  headers: { Accept: 'application/octet-stream, */*' },
135
136
  });
136
137
  if (response.status === 401 || response.status === 302) {
137
- throw blogAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录', { httpStatus: response.status });
138
+ throw blogAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
138
139
  }
139
140
  const contentType = response.contentType;
140
141
  const declaredLength = Number(response.headers.get('content-length'));
@@ -1,3 +1,4 @@
1
+ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
1
2
  import { requireE10Session } from '../../internal/e10/context.js';
2
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
3
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -152,7 +153,7 @@ export function createCaizhilianHost(ctx, dependencies = {}) {
152
153
  async function request(request) {
153
154
  const response = await doRequest(request, false).catch((error) => mapRuntimeNetworkError(error));
154
155
  if (response.status === 401 || response.status === 302) {
155
- throw caizhilianAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> 完成登录', { httpStatus: response.status });
156
+ throw caizhilianAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
156
157
  }
157
158
  if (response.parseError) {
158
159
  if (response.status < 200 || response.status >= 300) {
@@ -183,7 +184,7 @@ export function createCaizhilianHost(ctx, dependencies = {}) {
183
184
  async function requestBinary(request) {
184
185
  const response = await doRequest(request, true).catch((error) => mapRuntimeNetworkError(error));
185
186
  if (response.status === 401 || response.status === 302) {
186
- throw caizhilianAuthError('session_expired', 'E10 登录态已失效,请先登录', { httpStatus: response.status });
187
+ throw caizhilianAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
187
188
  }
188
189
  if (response.status < 200 || response.status >= 300) {
189
190
  throw new CaizhilianCliError('api', 'http_error', `采知连下载 HTTP ${response.status}`, {
@@ -19,7 +19,7 @@ const WITHHELD = [
19
19
  },
20
20
  {
21
21
  name: 'caizhilian.system.switch-config',
22
- reason: '源 switch_config 在 Skill 本地多套环境配置间切换;CLI 的环境切换由 `weaver-work-cli config set` / `weaver-work-cli auth login --base-url <域名>` 承担,业务模块不做环境切换。',
22
+ reason: '源 switch_config 在 Skill 本地多套环境配置间切换;CLI 的环境与认证配置体系负责这一职责,业务模块不做环境切换。',
23
23
  },
24
24
  ];
25
25
  function buildOperations() {
@@ -1,3 +1,4 @@
1
+ import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
1
2
  import { requireE10Session } from '../../internal/e10/context.js';
2
3
  import { getActiveProfile } from '../../internal/e10/auth/session.js';
3
4
  import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
@@ -110,7 +111,7 @@ export function createCalendarHost(ctx, dependencies = {}) {
110
111
  mapRuntimeNetworkError(error);
111
112
  }
112
113
  if (response.status === 401 || response.status === 302) {
113
- throw calendarAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> 完成登录', { httpStatus: response.status });
114
+ throw calendarAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
114
115
  }
115
116
  if (response.parseError) {
116
117
  if (response.status < 200 || response.status >= 300) {