weaver-work-cli 0.1.25 → 0.1.28

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 (173) hide show
  1. package/README.md +35 -30
  2. package/dist/cmd/root.js +1 -0
  3. package/dist/cmd/setup/index.js +1 -1
  4. package/dist/core/runtime-agent.js +19 -0
  5. package/dist/internal/e10/auth/commands.js +48 -33
  6. package/dist/internal/e10/auth/messages.js +3 -0
  7. package/dist/internal/e10/auth/session.js +8 -0
  8. package/dist/internal/e10/auth/xiaoe.js +62 -43
  9. package/dist/internal/e10/auth-recovery.js +148 -0
  10. package/dist/internal/e10/client.js +47 -15
  11. package/dist/internal/e10/context.js +9 -31
  12. package/dist/internal/e10/index.js +1 -0
  13. package/dist/internal/e10/request-runtime.js +51 -9
  14. package/dist/shortcuts/archive/host.js +3 -2
  15. package/dist/shortcuts/archive/manifest.js +1 -1
  16. package/dist/shortcuts/archive-oa/constants.js +66 -0
  17. package/dist/shortcuts/archive-oa/continuation.js +70 -0
  18. package/dist/shortcuts/archive-oa/errors.js +53 -0
  19. package/dist/shortcuts/archive-oa/host.js +158 -0
  20. package/dist/shortcuts/archive-oa/index.js +225 -0
  21. package/dist/shortcuts/archive-oa/manifest.js +62 -0
  22. package/dist/shortcuts/archive-oa/operations/borrow-car.js +159 -0
  23. package/dist/shortcuts/archive-oa/operations/borrow-url.js +108 -0
  24. package/dist/shortcuts/archive-oa/operations/confirm.js +48 -0
  25. package/dist/shortcuts/archive-oa/operations/contract.js +120 -0
  26. package/dist/shortcuts/archive-oa/operations/locate.js +50 -0
  27. package/dist/shortcuts/archive-oa/operations/registry.js +40 -0
  28. package/dist/shortcuts/archive-oa/operations/shared.js +181 -0
  29. package/dist/shortcuts/archive-oa/operations/types.js +45 -0
  30. package/dist/shortcuts/archive-oa/operations.js +13 -0
  31. package/dist/shortcuts/asset/host.js +7 -6
  32. package/dist/shortcuts/blog/host.js +4 -3
  33. package/dist/shortcuts/caizhilian/host.js +3 -2
  34. package/dist/shortcuts/caizhilian/manifest.js +1 -1
  35. package/dist/shortcuts/calendar/host.js +2 -1
  36. package/dist/shortcuts/contract/host.js +34 -59
  37. package/dist/shortcuts/ehr/host.js +2 -1
  38. package/dist/shortcuts/esb/host.js +2 -1
  39. package/dist/shortcuts/fna/host.js +6 -3
  40. package/dist/shortcuts/fna/manifest.js +10 -5
  41. package/dist/shortcuts/fna/operations/expense-chart.js +162 -0
  42. package/dist/shortcuts/fna/operations/registry.js +2 -0
  43. package/dist/shortcuts/hrm/host.js +2 -1
  44. package/dist/shortcuts/im/host.js +3 -2
  45. package/dist/shortcuts/index.js +2 -0
  46. package/dist/shortcuts/invoice/host.js +9 -7
  47. package/dist/shortcuts/invoice/manifest.js +38 -47
  48. package/dist/shortcuts/invoice/operations/issuing-qys.js +328 -0
  49. package/dist/shortcuts/invoice/operations/registry.js +2 -3
  50. package/dist/shortcuts/jiuchuanhui/host.js +2 -1
  51. package/dist/shortcuts/mail/host.js +2 -1
  52. package/dist/shortcuts/meeting/host.js +2 -1
  53. package/dist/shortcuts/meeting/operations/eb/conflict.js +78 -12
  54. package/dist/shortcuts/okr/host.js +2 -1
  55. package/dist/shortcuts/plan/host.js +2 -1
  56. package/dist/shortcuts/project/host.js +2 -1
  57. package/dist/shortcuts/workflow/endpoints.js +11 -0
  58. package/dist/shortcuts/workflow/host.js +5 -7
  59. package/dist/shortcuts/workflow/index.js +6 -36
  60. package/dist/shortcuts/workflow/operations/chatscene.js +252 -0
  61. package/dist/shortcuts/workflow/operations/form.js +1047 -191
  62. package/dist/shortcuts/workflow/operations/read.js +137 -8
  63. package/dist/shortcuts/workflow/operations/sign-input.js +106 -0
  64. package/dist/shortcuts/workflow/operations/write.js +9 -1
  65. package/dist/shortcuts/workflow/stdout-compact.js +16 -1
  66. package/docs/SKILL.md +1 -1
  67. package/docs/_catalog.md +1 -1
  68. package/docs/agent-invoice.md +1 -1
  69. package/docs/archive-oa.md +79 -0
  70. package/docs/caizhilian.md +1 -1
  71. package/docs/e10-auth.md +7 -6
  72. package/docs/fna.md +7 -3
  73. package/docs/invoice.md +4 -6
  74. package/package.json +1 -1
  75. package/skill-template/business-info.json +20 -1
  76. package/skill-template/domains/archive-oa.md +19 -0
  77. package/skill-template/domains/caizhilian.md +2 -1
  78. package/skill-template/domains/qiyecheng.md +1 -0
  79. package/skill-template/domains/yepiaotong.md +8 -4
  80. package/skill-template/skill-template.md +1 -0
  81. package/skills/weaver-e10-archive-oa/SKILL.md +159 -0
  82. package/skills/weaver-e10-archive-oa/references/archive-locate.md +49 -0
  83. package/skills/weaver-e10-archive-oa/references/borrow-car.md +76 -0
  84. package/skills/weaver-e10-archive-oa/references/borrow-flow.md +53 -0
  85. package/skills/weaver-e10-archive-oa/references/contract-search.md +63 -0
  86. package/skills/weaver-e10-archive-oa/references/disabled-capabilities.md +29 -0
  87. package/skills/weaver-e10-archive-oa/references/qys-contract-search.md +47 -0
  88. package/skills/weaver-e10-archive-oa/references/source-manifest.json +400 -0
  89. package/skills/weaver-e10-blog/SKILL.md +6 -1
  90. package/skills/weaver-e10-blog/references/blog-query.md +3 -1
  91. package/skills/weaver-e10-builder/SKILL.md +22 -0
  92. package/skills/weaver-e10-caizhilian/SKILL.md +47 -16
  93. package/skills/weaver-e10-caizhilian/references/action-flow.md +70 -0
  94. package/skills/weaver-e10-caizhilian/references/agent-entry.md +12 -4
  95. package/skills/weaver-e10-caizhilian/references/auth-and-install.md +207 -0
  96. package/skills/weaver-e10-caizhilian/references/collaboration.md +5 -3
  97. package/skills/weaver-e10-caizhilian/references/document.md +33 -11
  98. package/skills/weaver-e10-caizhilian/references/file-collection.md +57 -54
  99. package/skills/weaver-e10-caizhilian/references/file-upload.md +5 -6
  100. package/skills/weaver-e10-caizhilian/references/files.md +9 -3
  101. package/skills/weaver-e10-caizhilian/references/folder.md +48 -16
  102. package/skills/weaver-e10-caizhilian/references/host-executor-prompt.md +32 -0
  103. package/skills/weaver-e10-caizhilian/references/host-platform-integration.md +46 -0
  104. package/skills/weaver-e10-caizhilian/references/rule-index.md +56 -0
  105. package/skills/weaver-e10-caizhilian/references/setting-attach.md +17 -54
  106. package/skills/weaver-e10-caizhilian/references/setting-backup.md +16 -63
  107. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +24 -64
  108. package/skills/weaver-e10-caizhilian/references/setting-storage-docs-summary.md +126 -0
  109. package/skills/weaver-e10-caizhilian/references/setting-storage-field-map.md +57 -0
  110. package/skills/weaver-e10-caizhilian/references/setting-storage.md +143 -548
  111. package/skills/weaver-e10-caizhilian/references/source-manifest.json +7 -7
  112. package/skills/weaver-e10-caizhilian/references/task-workflow.md +87 -0
  113. package/skills/weaver-e10-caizhilian/references/withheld.md +21 -45
  114. package/skills/weaver-e10-caizhilian/references/write-protocol.md +7 -0
  115. package/skills/weaver-e10-calendar/SKILL.md +6 -1
  116. package/skills/weaver-e10-esb/SKILL.md +8 -0
  117. package/skills/weaver-e10-esb/references/input-format.md +3 -1
  118. package/skills/weaver-e10-esb/references/source-manifest.json +5 -5
  119. package/skills/weaver-e10-hrm/SKILL.md +6 -1
  120. package/skills/weaver-e10-hrm/references/employee.md +1 -1
  121. package/skills/weaver-e10-hrm/references/org.md +1 -1
  122. package/skills/weaver-e10-hrm/references/position.md +1 -1
  123. package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
  124. package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +1 -1
  125. package/skills/weaver-e10-jiuchuanhui/SKILL.md +6 -1
  126. package/skills/weaver-e10-jucailin/SKILL.md +6 -1
  127. package/skills/weaver-e10-jucailin/references/hr-contact.md +1 -1
  128. package/skills/weaver-e10-jucailin/references/salary.md +1 -1
  129. package/skills/weaver-e10-mail/SKILL.md +6 -1
  130. package/skills/weaver-e10-meeting/SKILL.md +11 -6
  131. package/skills/weaver-e10-meeting/references/meeting-eb.md +7 -3
  132. package/skills/weaver-e10-meeting/references/meeting-queries.md +3 -1
  133. package/skills/weaver-e10-meeting/references/meeting-verification.md +4 -2
  134. package/skills/weaver-e10-okr/SKILL.md +6 -1
  135. package/skills/weaver-e10-okr/references/source-manifest.json +5 -5
  136. package/skills/weaver-e10-plan/SKILL.md +6 -1
  137. package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +3 -1
  138. package/skills/weaver-e10-plan/references/plan-report-query.md +3 -1
  139. package/skills/weaver-e10-plan/references/source-manifest.json +7 -7
  140. package/skills/weaver-e10-qiyecheng/SKILL.md +16 -6
  141. package/skills/weaver-e10-qiyecheng/references/catalog.md +6 -3
  142. package/skills/weaver-e10-qiyecheng/references/expense-center.md +2 -0
  143. package/skills/weaver-e10-qiyecheng/references/expense-chart.md +78 -0
  144. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +87 -33
  145. package/skills/weaver-e10-shared/SKILL.md +53 -28
  146. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +22 -20
  147. package/skills/weaver-e10-shared/references/non-interactive-environment.md +13 -13
  148. package/skills/weaver-e10-shared/references/weaver-e10-installation.md +3 -1
  149. package/skills/weaver-e10-wenshuding/SKILL.md +6 -1
  150. package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
  151. package/skills/weaver-e10-workflow/references/source-manifest.json +33 -24
  152. package/skills/weaver-e10-workflow/references/workflow-create.md +41 -26
  153. package/skills/weaver-e10-yepiaotong/SKILL.md +23 -18
  154. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +87 -45
  155. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +152 -0
  156. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +210 -0
  157. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +59 -36
  158. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +63 -512
  159. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +205 -346
  160. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -5
  161. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +231 -344
  162. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +5 -5
  163. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +201 -162
  164. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +121 -145
  165. package/skills/weaver-e10-yimiaoban/SKILL.md +9 -2
  166. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +1 -1
  167. package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -1
  168. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +9 -9
  169. package/skills/weaver-e10-ziguanjia/SKILL.md +6 -1
  170. package/dist/shortcuts/invoice/operations/issuing.js +0 -198
  171. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-field-rules.md +0 -161
  172. package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +0 -107
  173. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +0 -119
package/README.md CHANGED
@@ -211,41 +211,46 @@ weaver-work-cli auth login --base-url "https://weapp.xxx.cn"
211
211
  weaver-work-cli auth oidc --base-url "https://weapp.xxx.cn"
212
212
  ```
213
213
 
214
- 只有当前智能体身份明确为 XiaoE 时,`auth xiaoe` 和未显式指定认证来源的业务命令才会优先使用平台注入的
215
- `XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 换取 ETEAMSID:其中 `XIAOE_DOMAIN` 是
216
- XiaoE/erpa 换票服务地址,不是 E10 登录地址。CLI 的 Node 实现会直接调用与
217
- `xiaoe_env.py` / `tokens.py` 相同的换票接口,并校验业务码和 `data.eteamId`;它保留
218
- 系统 CA 校验,且只允许同源重定向或同主机 HTTP→HTTPS 升级。平台凭证不可用时,
219
- CLI 会先在内部调用 npm 包自带的 `scripts/xiaoe_env.py` 兼容路径,再按顺序尝试
220
- 宿主 `xiaoeUserData/tokens.py`、桌面 XiaoE 客户端当前会话
221
- (优先读取宿主注入的 `XIAOE_USER_SETTINGS_PATH`,其次查找
222
- `XIAOE_ROBOT_DATA_ROOT` / `XIAOE_ROBOT_ROOT`,最后回退
223
- `XiaoE/Robots/<id>/user_settings.json`)和 `WEAVER_XIAOE_*` 环境映射。只要会话文件中的
224
- `session_id` 与 `erpa` 有效,Node 路径即可直接换票,不要求宿主存在 `tokens.py`。
225
- `xiaoe_env.py` 由 CLI 独占执行并解析,不放进 shared Skill,也不把 stdout 凭证暴露给
226
- Agent;Skill 只负责调用 `weaver-work-cli auth xiaoe`。CLI 自带脚本仅依赖 Python
227
- 标准库,仍保持 TLS 证书校验,不会采用关闭证书校验的回退。
228
-
229
- XiaoE 桌面客户端不一定注入上述 Token 对;它会注入 `XIAOE_DATA_ROOT`、
230
- `XIAOE_ROBOT_DATA_ROOT`、`XIAOE_USER_SETTINGS_PATH`、`XIAOE_ROBOT_ROOT` 等精确的
231
- 运行时路径标记,CLI 也把这些标记识别为当前 XiaoE 桌面宿主。普通 `XIAOE_SKILL_*`、
232
- `WEAVER_XIAOE_*` 配置或仅在磁盘上发现客户端文件仍不构成宿主身份。
233
-
234
- E10 目标地址按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*` E10 配置/映射 →
235
- 当前 profile → `https://www.e-cology.com.cn`”解析,绝不会把 `XIAOE_DOMAIN`
236
- 当作 E10 地址。匹配成功时直接保存登录态,并固定使用 `agentType=XiaoE`。业务命令
237
- 在已确认的 XiaoE 智能体环境中且未显式指定 `--auth` / `--profile` 时,也会自动优先尝试 XiaoE 统一登录。
238
- `auth xiaoe` 失败后仅在 macOS/Windows 桌面宿主自动降级浏览器 OAuth;Web/Linux
239
- 环境会返回结构化的 `authentication/xiaoe_web_credential_unavailable` 错误,不再
240
- 启动无法回调的 localhost 浏览器登录。确需浏览器授权时,应在可接收本地回调的宿主
241
- 显式运行 `auth login`。也可以显式使用:
214
+ XiaoE 环境保留两种识别入口:桌面客户端由 Agent 根据宿主/任务的明确上下文判断,并在
215
+ 业务命令上传 `--runtime-agent xiaoe`;网页端沿用原有环境变量规则,成对的
216
+ `XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 会被识别为 XiaoE。判定为 XiaoE 后,Agent 显式执行:
242
217
 
243
218
  ```text
244
219
  weaver-work-cli auth xiaoe
245
220
  ```
246
221
 
247
- `auth login` / `auth oidc` 始终是纯浏览器 OAuth 入口。本机存在 `xiaoeUserData/tokens.py`、XiaoE 客户端目录、`XIAOE_SKILL_*` 或 `WEAVER_XIAOE_*` 配置,都不能单独证明当前智能体是 XiaoE,也不会让 WorkBuddy、Codex 或其他智能体切换到 XiaoE 登录。
248
- `auth xiaoe` 会先输出不含凭证的 preflight 摘要(`hostDetected`、`attempted`、`tokenScriptDetected`、`clientSettingsDetected`、`baseUrl`);命中 `tokens.py` 或内置 Node 客户端会话路径时还会输出明确的尝试日志。统一登录成功后,登录结果以及后续 `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、`credentialSourceLabel`,可据此确认实际凭证来源;`3 / xiaoe_client / XiaoE client settings` 表示未依赖 `tokens.py`,由 Node 直接换票。
222
+ `auth xiaoe` 本身就是认证方式的显式选择。`hasXiaoEEnv` 通过桌面端显式参数或网页端原有
223
+ 环境变量规则判断;工作目录、客户端文件或历史登录仍不作为依据。CLI 按顺序消费平台凭据、CLI 自带兼容脚本、宿主 `tokens.py`、
224
+ 桌面 XiaoE 客户端会话和 `WEAVER_XIAOE_*` 映射。`XIAOE_DOMAIN` 是 XiaoE/erpa 换票
225
+ 服务地址,不是 E10 地址。E10 目标按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*`
226
+ E10 配置/映射 → 当前 profile → `https://www.e-cology.com.cn`”解析。
227
+
228
+ 统一登录失败时,所有操作系统都返回结构化的
229
+ `authentication/xiaoe_credential_unavailable`,不会自动打开浏览器或降级到
230
+ `auth login`。确需浏览器授权时,调用方必须另行显式执行 `auth login`。
231
+
232
+ 业务命令不会在没有本地会话时主动建立 XiaoE 登录态,因此首次使用仍需显式执行
233
+ `auth xiaoe`。已有会话在接口层被明确判定为失效(HTTP 401/302、业务码 `-2` 或登录页)时,
234
+ 公共 E10 请求运行时会静默换取一次新会话并把被拒绝的请求重放一次;这个过程不向
235
+ stdout/stderr 输出中间“登录”步骤,并会合并同一 profile 的并发换票。流式请求、网络错误、
236
+ `partial` / `write_uncertain` 不会重放;恢复后的地址或用户身份变化时也会停止。
237
+
238
+ 桌面客户端需要 XiaoE 专属业务行为时,把全局参数放在业务子命令之前,例如:
239
+
240
+ ```text
241
+ weaver-work-cli --runtime-agent xiaoe workflow ...
242
+ ```
243
+
244
+ 网页端不要求补这个参数,仍由环境变量规则识别。`--runtime-agent` 只表示当前运行时类型,
245
+ 不携带也不选择凭据。`auth login` / `auth oidc`
246
+ 始终是纯浏览器 OAuth 入口,不会读取 XiaoE 凭据。
247
+
248
+ `auth xiaoe` 会先输出不含凭证的 preflight 摘要,显示 `authMode=xiaoe`、
249
+ `credentialSourceDetected`、`attempted` 及各凭据源检测结果;命中 `tokens.py` 或内置
250
+ Node 客户端会话路径时还会输出明确的尝试日志。统一登录成功后,登录结果以及后续
251
+ `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、
252
+ `credentialSourceLabel`,可据此确认实际凭证来源;`3 / xiaoe_client / XiaoE client settings`
253
+ 表示未依赖 `tokens.py`,由 Node 直接换票。
249
254
 
250
255
  默认本地回调地址为 `http://127.0.0.1:19800/callback`。如果端口被占用:
251
256
 
package/dist/cmd/root.js CHANGED
@@ -29,6 +29,7 @@ export function createProgram() {
29
29
  .option('--config <path>', 'use a custom weaver-work-cli config file')
30
30
  .option('--auth <path>', 'use a specific E10 auth file')
31
31
  .option('--profile <name>', 'use a specific E10 profile')
32
+ .option('--runtime-agent <name>', 'identify the current agent runtime (for example: xiaoe)')
32
33
  .option('--is-connector [value]', 'run as the WorkBuddy connector: use the fixed E10 profile managed by the connector')
33
34
  .option('--base-url <url>', 'override E10 base URL')
34
35
  .option('--passport-url <url>', 'override E10 passport URL');
@@ -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');
@@ -0,0 +1,19 @@
1
+ const XIAOE_RUNTIME_AGENT_ALIASES = new Set([
2
+ 'xiaoe',
3
+ 'xiaoeclient',
4
+ '小e',
5
+ '小e客户端',
6
+ '智能小e',
7
+ '智能小e客户端',
8
+ ]);
9
+ export function normalizeRuntimeAgent(value) {
10
+ if (typeof value !== 'string')
11
+ return '';
12
+ return value.toLowerCase().replace(/[^a-z0-9\p{Script=Han}]+/gu, '');
13
+ }
14
+ export function isXiaoERuntimeAgent(value) {
15
+ return XIAOE_RUNTIME_AGENT_ALIASES.has(normalizeRuntimeAgent(value));
16
+ }
17
+ export function isXiaoERuntime(options) {
18
+ return isXiaoERuntimeAgent(options?.runtimeAgent);
19
+ }
@@ -5,8 +5,8 @@ import { CliError, ExitCode } from '../../../core/errors.js';
5
5
  import { clearInvoiceTokenForSession } from '../../../shortcuts/invoice/host.js';
6
6
  import { buildE10CookieHeader, E10AuthSession, clearProfileAuthFile, extractRawSetCookie, extractSessionTicket, findAuthFile, getActiveProfile, getProfileAuthPath, getProfileDir, listProfiles, profileNameFromUrl, readProfileConfig, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, switchActiveProfile, } from './session.js';
7
7
  import { getE10AuthRoot } from './paths.js';
8
- import { XIAOE_AGENT_TYPE, createXiaoEUnifiedLoginUnavailableError, hasXiaoEClientCredential, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEPreferredBaseUrl, shouldUseXiaoEBrowserFallback, tryXiaoEUnifiedLogin as tryXiaoEUnifiedSessionLogin, } from './xiaoe.js';
9
- export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential, shouldUseXiaoEBrowserFallback, } from './xiaoe.js';
8
+ import { XIAOE_AGENT_TYPE, createXiaoEUnifiedLoginUnavailableError, hasXiaoEEnv, inspectXiaoEUnifiedLoginSources, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin as tryXiaoEUnifiedSessionLogin, } from './xiaoe.js';
9
+ export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, hasXiaoEUnifiedLoginSource, inspectXiaoEUnifiedLoginSources, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential, } from './xiaoe.js';
10
10
  const DEFAULT_AGENT_TYPE = 'weaver-work-cli';
11
11
  const EXPLICIT_AGENT_TYPE_ENV_KEYS = [
12
12
  'WORKBUDDY_AGENT_TYPE',
@@ -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.',
@@ -480,13 +475,16 @@ async function oidcExchange(code, redirectUri, baseUrl, tenantKeyFallback, agent
480
475
  version,
481
476
  };
482
477
  }
483
- function xiaoEUnifiedLoginUnavailableMessage(baseUrl, willOpenBrowser) {
478
+ function xiaoEUnifiedLoginUnavailableMessage(baseUrl, attempted) {
484
479
  return [
485
- willOpenBrowser
486
- ? '未获取到可用的 XiaoE 统一登录信息,准备降级打开网页登录。'
487
- : '未获取到可用的 XiaoE 统一登录信息;当前 Web/Linux 环境不支持本机浏览器回调,已停止自动降级。',
480
+ attempted
481
+ ? 'XiaoE 统一登录已尝试,但未获取到可用会话。'
482
+ : '未检测到可用的 XiaoE 统一登录凭据源,本次未尝试统一登录。',
483
+ '`auth xiaoe` 不会降级到浏览器 OAuth。',
488
484
  baseUrl ? `当前 E10 baseUrl: ${baseUrl}。` : '未从 XiaoE 凭证中解析到 E10 baseUrl。',
489
- 'CLI 已依次尝试平台注入凭证的 Node 换票、CLI 内置 xiaoe_env.py 兼容换票、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。',
485
+ attempted
486
+ ? 'CLI 已依次尝试平台注入凭证的 Node 换票、CLI 内置 xiaoe_env.py 兼容换票、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。'
487
+ : 'CLI 已检查平台注入凭证、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。',
490
488
  'CLI 会优先使用 XiaoE 客户端内置 Python 执行 tokens.py;自动发现不可用时,可用 WEAVER_XIAOE_TOKEN_SCRIPT_PYTHON 指定兼容解释器。',
491
489
  ].join(' ');
492
490
  }
@@ -496,30 +494,49 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
496
494
  const profileFromGlobal = globalOptions.profile;
497
495
  const cfg = readProfileConfig(profileFromGlobal || getActiveProfile());
498
496
  const explicitBaseUrl = optionString(commandOpts, 'baseUrl') || globalOptions.baseUrl || '';
499
- const xiaoEHostDetected = hasXiaoEEnv(ctx.env);
500
- const useXiaoELogin = behavior.tryXiaoEUnifiedLogin === true && xiaoEHostDetected;
501
- const baseUrl = useXiaoELogin
502
- ? resolveXiaoEPreferredBaseUrl({
503
- explicitBaseUrl,
504
- profileBaseUrl: cfg.baseUrl,
497
+ const preferredXiaoEBaseUrl = resolveXiaoEPreferredBaseUrl({
498
+ explicitBaseUrl,
499
+ profileBaseUrl: cfg.baseUrl,
500
+ env: ctx.env,
501
+ });
502
+ const xiaoESources = behavior.tryXiaoEUnifiedLogin === true
503
+ ? inspectXiaoEUnifiedLoginSources({
505
504
  env: ctx.env,
505
+ baseUrl: preferredXiaoEBaseUrl,
506
+ profileBaseUrl: cfg.baseUrl,
507
+ allowTokenScript: behavior.allowXiaoETokenScript === true,
506
508
  })
509
+ : {
510
+ platformCredential: false,
511
+ tokenScript: false,
512
+ clientCredential: false,
513
+ environmentCredential: false,
514
+ any: false,
515
+ };
516
+ const useXiaoELogin = behavior.tryXiaoEUnifiedLogin === true && xiaoESources.any;
517
+ const baseUrl = behavior.tryXiaoEUnifiedLogin
518
+ ? preferredXiaoEBaseUrl
507
519
  : normalizeUrl(explicitBaseUrl || cfg.baseUrl || '');
508
520
  const tenantKey = optionString(commandOpts, 'tenantKey');
509
521
  const portText = optionString(commandOpts, 'port') || '19800';
510
522
  const port = Number.parseInt(portText, 10);
511
523
  const explicitAgentType = optionString(commandOpts, 'agentType', 'agent_type');
512
- const agentType = resolveAgentType(commandOpts, ctx.env);
524
+ const agentType = useXiaoELogin
525
+ ? optionString(commandOpts, 'agentType', 'agent_type') || XIAOE_AGENT_TYPE
526
+ : resolveAgentType(commandOpts, ctx.env);
513
527
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
514
528
  throw new CliError('Invalid --port value', ExitCode.UsageError);
515
529
  }
516
530
  if (behavior.tryXiaoEUnifiedLogin) {
517
531
  ctx.logger.info([
518
532
  'XiaoE unified login preflight:',
519
- `hostDetected=${String(xiaoEHostDetected)}`,
533
+ 'authMode=xiaoe',
534
+ `credentialSourceDetected=${String(xiaoESources.any)}`,
520
535
  `attempted=${String(useXiaoELogin)}`,
521
- `tokenScriptDetected=${String(hasXiaoETokenScript(ctx.env))}`,
522
- `clientSettingsDetected=${String(hasXiaoEClientCredential(ctx.env))}`,
536
+ `platformCredentialDetected=${String(xiaoESources.platformCredential)}`,
537
+ `tokenScriptDetected=${String(xiaoESources.tokenScript)}`,
538
+ `clientSettingsDetected=${String(xiaoESources.clientCredential)}`,
539
+ `environmentCredentialDetected=${String(xiaoESources.environmentCredential)}`,
523
540
  `baseUrl=${baseUrl || '(unresolved)'}`,
524
541
  ].join(' '));
525
542
  }
@@ -528,11 +545,9 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
528
545
  if (xiaoEResult)
529
546
  return xiaoEResult;
530
547
  }
531
- const willOpenBrowser = !useXiaoELogin
532
- || behavior.browserFallback !== 'desktop-only'
533
- || shouldUseXiaoEBrowserFallback();
534
- if (useXiaoELogin && behavior.explainXiaoEFallback) {
535
- ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, willOpenBrowser));
548
+ const willOpenBrowser = behavior.browserFallback !== 'never';
549
+ if (behavior.tryXiaoEUnifiedLogin && behavior.explainXiaoEFallback) {
550
+ ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, useXiaoELogin));
536
551
  }
537
552
  if (!willOpenBrowser) {
538
553
  throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
@@ -714,15 +729,15 @@ function formatWhoami(title, data) {
714
729
  ].join('\n');
715
730
  }
716
731
  function registerBrowserLoginCommand(auth, action, name, description, behavior = {}) {
717
- auth.command(name)
732
+ const command = auth.command(name)
718
733
  .description(description)
719
734
  .option('--base-url <url>', 'E10 base URL')
720
735
  .option('--tenant-key <key>', 'Tenant key fallback after login')
721
736
  .option('--port <n>', 'Local OAuth callback port', '19800')
722
737
  .option('--agent_type <value>', 'Agent type sent to E10 authorize endpoint')
723
738
  .option('--agent-type <value>', 'Alias of --agent_type')
724
- .option('--agent-return-url <url>', 'Desktop application URL linked by the login success page')
725
- .action(action(async (ctx, options) => {
739
+ .option('--agent-return-url <url>', 'Desktop application URL linked by the login success page');
740
+ command.action(action(async (ctx, options) => {
726
741
  const result = await runBrowserLogin(ctx, options, behavior);
727
742
  ctx.output.write(result, { text: formatLoginResult(result) });
728
743
  }));
@@ -808,11 +823,11 @@ export function registerE10AuthCommands(parent, action) {
808
823
  }));
809
824
  registerBrowserLoginCommand(auth, action, 'login', 'Open browser OAuth2.0 login and save E10 auth');
810
825
  registerBrowserLoginCommand(auth, action, 'oidc', 'Alias of auth login for OAuth2.0 browser login');
811
- registerBrowserLoginCommand(auth, action, 'xiaoe', 'Try XiaoE unified login and explain before browser OAuth fallback', {
826
+ registerBrowserLoginCommand(auth, action, 'xiaoe', 'Use XiaoE unified login without browser OAuth fallback', {
812
827
  tryXiaoEUnifiedLogin: true,
813
828
  allowXiaoETokenScript: true,
814
829
  explainXiaoEFallback: true,
815
- browserFallback: 'desktop-only',
830
+ browserFallback: 'never',
816
831
  });
817
832
  auth.command('logout')
818
833
  .alias('clear')
@@ -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 } : {}),
@@ -4,6 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { CliError, ExitCode } from '../../../core/errors.js';
7
+ import { isXiaoERuntime, isXiaoERuntimeAgent } from '../../../core/runtime-agent.js';
7
8
  import { buildE10CookieHeader, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
8
9
  export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
9
10
  export const XIAOE_AGENT_TYPE = 'XiaoE';
@@ -52,12 +53,6 @@ const XIAOE_CLIENT_SETTINGS_ROOT_ENV_KEYS = [
52
53
  'XIAOE_ROBOT_DATA_ROOT',
53
54
  'XIAOE_ROBOT_ROOT',
54
55
  ];
55
- const XIAOE_DESKTOP_HOST_ENV_KEYS = [
56
- 'XIAOE_DATA_ROOT',
57
- 'XIAOE_ROBOT_DATA_ROOT',
58
- 'XIAOE_USER_SETTINGS_PATH',
59
- 'XIAOE_ROBOT_ROOT',
60
- ];
61
56
  export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
62
57
  export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
63
58
  export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
@@ -70,36 +65,29 @@ function envString(env, ...keys) {
70
65
  }
71
66
  return '';
72
67
  }
73
- export function hasXiaoEEnv(env = process.env) {
74
- const isXiaoEAgentType = (value) => {
75
- const normalized = value.toLowerCase().replace(/[^a-z0-9\p{Script=Han}]+/gu, '');
76
- return ['xiaoe', 'xiaoeclient', '小e', '智能小e'].includes(normalized);
77
- };
78
- // A host-specific identity is authoritative. Generic WORKBUDDY_*/CODEX_*
79
- // variables are only framework markers and can also be present when XiaoE
80
- // launches the CLI through a shared agent runtime.
81
- const explicitHostAgentType = envString(env, 'WORKBUDDY_AGENT_TYPE', 'CODEX_AGENT_TYPE');
82
- if (explicitHostAgentType)
83
- return isXiaoEAgentType(explicitHostAgentType);
84
- // XiaoE injects this pair into the command process for the current run. A
85
- // tokens.py path, client installation, XIAOE_SKILL_* variable, or
86
- // WEAVER_XIAOE_* configuration alone is credential availability, not proof
87
- // that the current agent is XiaoE.
88
- if (hasXiaoEPlatformCredential(env))
68
+ /**
69
+ * Identifies the XiaoE runtime without inspecting credential artifacts:
70
+ * desktop callers may pass --runtime-agent, and any injected XIAOE_* variable
71
+ * is treated as XiaoE host evidence.
72
+ */
73
+ export function hasXiaoEEnv(env = process.env, options = {}) {
74
+ if (isXiaoERuntime(options))
89
75
  return true;
90
- // The desktop XiaoE client injects these exact runtime paths even when it
91
- // does not inject the web/sandbox XIAOE_USER_TOKEN + XIAOE_DOMAIN pair.
92
- // They identify the current command host; unrelated XIAOE_SKILL_* variables
93
- // and merely discovering local XiaoE files still do not.
94
- if (XIAOE_DESKTOP_HOST_ENV_KEYS.some((key) => Boolean(envString(env, key))))
76
+ // XiaoE reserves this prefix for host-injected runtime variables. Presence
77
+ // of any matching key is sufficient; the value may legitimately be empty.
78
+ if (Object.keys(env).some((key) => key.startsWith('XIAOE_')))
95
79
  return true;
96
- // Current-host framework markers win over WEAVER_AGENT_TYPE, which can be
97
- // inherited from a previously saved E10 profile by `weaver-work-cli run`.
80
+ // An explicitly named host remains authoritative over credential signals.
81
+ const explicitHostAgentType = envString(env, 'WORKBUDDY_AGENT_TYPE', 'CODEX_AGENT_TYPE');
82
+ if (explicitHostAgentType)
83
+ return isXiaoERuntimeAgent(explicitHostAgentType);
84
+ // Generic framework markers can coexist with stale profile metadata, so they
85
+ // take precedence over WEAVER_AGENT_TYPE when no XIAOE_* variable is present.
98
86
  if (Object.keys(env).some((key) => key.startsWith('WORKBUDDY_') || key.startsWith('CODEX_'))) {
99
87
  return false;
100
88
  }
101
89
  const explicitAgentType = envString(env, 'WEAVER_AGENT_TYPE');
102
- return explicitAgentType ? isXiaoEAgentType(explicitAgentType) : false;
90
+ return explicitAgentType ? isXiaoERuntimeAgent(explicitAgentType) : false;
103
91
  }
104
92
  export function describeXiaoECredentialSource(source) {
105
93
  // Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
@@ -311,7 +299,7 @@ export function resolveXiaoEDefaultBaseUrl(env = process.env) {
311
299
  const configured = resolveXiaoEConfiguredBaseUrl(env);
312
300
  if (configured)
313
301
  return configured;
314
- return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
302
+ return DEFAULT_E10_BASE_URL;
315
303
  }
316
304
  function uniqueStrings(values) {
317
305
  const seen = new Set();
@@ -350,10 +338,12 @@ export function xiaoEBundledEnvScriptPath() {
350
338
  const scriptPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../scripts/xiaoe_env.py');
351
339
  return existsSync(scriptPath) ? scriptPath : '';
352
340
  }
353
- export function hasXiaoETokenScript(env = process.env) {
341
+ export function hasXiaoETokenScript(env = process.env, allowLocalDiscovery = true) {
354
342
  const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
355
343
  if (explicit)
356
344
  return existsSync(explicit);
345
+ if (!allowLocalDiscovery)
346
+ return false;
357
347
  const scriptPath = xiaoETokenScriptPath(env);
358
348
  return Boolean(scriptPath && existsSync(scriptPath));
359
349
  }
@@ -620,7 +610,7 @@ function sortedXiaoEClientSettingsFiles(root) {
620
610
  const files = listXiaoEClientSettingsFiles(root);
621
611
  return files.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs);
622
612
  }
623
- function xiaoEClientSettingsCandidateFiles(env) {
613
+ function xiaoEClientSettingsCandidateFiles(env, allowLocalDiscovery = true) {
624
614
  const candidates = [];
625
615
  const explicitSettingsPath = envString(env, XIAOE_USER_SETTINGS_PATH_ENV_KEY);
626
616
  if (isFile(explicitSettingsPath))
@@ -629,7 +619,9 @@ function xiaoEClientSettingsCandidateFiles(env) {
629
619
  const injectedRoot = envString(env, key);
630
620
  candidates.push(...sortedXiaoEClientSettingsFiles(injectedRoot));
631
621
  }
632
- candidates.push(...sortedXiaoEClientSettingsFiles(xiaoEClientRobotsRoot(env)));
622
+ if (allowLocalDiscovery) {
623
+ candidates.push(...sortedXiaoEClientSettingsFiles(xiaoEClientRobotsRoot(env)));
624
+ }
633
625
  return uniqueStrings(candidates);
634
626
  }
635
627
  export function findXiaoEClientSettingsFile(env = process.env) {
@@ -674,8 +666,38 @@ export function readXiaoEClientCredential(env = process.env) {
674
666
  }
675
667
  return null;
676
668
  }
677
- export function hasXiaoEClientCredential(env = process.env) {
678
- return Boolean(findXiaoEClientSettingsFile(env));
669
+ export function hasXiaoEClientCredential(env = process.env, allowLocalDiscovery = true) {
670
+ return Boolean(xiaoEClientSettingsCandidateFiles(env, allowLocalDiscovery)[0]);
671
+ }
672
+ /**
673
+ * Reports usable unified-login credential sources independently from host
674
+ * identity. Installing the XiaoE client is not proof that the current process
675
+ * was launched by XiaoE, but an explicit `auth xiaoe` command may still consume
676
+ * any credential source that is available.
677
+ */
678
+ export function inspectXiaoEUnifiedLoginSources(options = {}) {
679
+ const env = options.env || process.env;
680
+ const baseUrl = resolveXiaoEPreferredBaseUrl({
681
+ explicitBaseUrl: options.baseUrl,
682
+ profileBaseUrl: options.profileBaseUrl,
683
+ env,
684
+ });
685
+ const platformCredential = hasXiaoEPlatformCredential(env);
686
+ const allowLocalDiscovery = options.allowLocalDiscovery !== false;
687
+ const tokenScript = options.allowTokenScript !== false
688
+ && hasXiaoETokenScript(env, allowLocalDiscovery);
689
+ const clientCredential = hasXiaoEClientCredential(env, allowLocalDiscovery);
690
+ const environmentCredential = Boolean(baseUrl && resolveXiaoEUnifiedLoginCredential(baseUrl, env));
691
+ return {
692
+ platformCredential,
693
+ tokenScript,
694
+ clientCredential,
695
+ environmentCredential,
696
+ any: platformCredential || tokenScript || clientCredential || environmentCredential,
697
+ };
698
+ }
699
+ export function hasXiaoEUnifiedLoginSource(options = {}) {
700
+ return inspectXiaoEUnifiedLoginSources(options).any;
679
701
  }
680
702
  async function fetchXiaoEClientEteamId(credential, domain) {
681
703
  const controller = new AbortController();
@@ -769,22 +791,19 @@ export function resolveXiaoEPreferredBaseUrl(options) {
769
791
  const profileBaseUrl = normalizeUrl(options.profileBaseUrl || '');
770
792
  if (profileBaseUrl)
771
793
  return profileBaseUrl;
772
- return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
773
- }
774
- export function shouldUseXiaoEBrowserFallback(platform = process.platform) {
775
- return platform === 'darwin' || platform === 'win32';
794
+ return DEFAULT_E10_BASE_URL;
776
795
  }
777
796
  export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
778
797
  const target = normalizeUrl(baseUrl);
779
798
  const message = [
780
799
  'XiaoE unified login did not return a usable E10 session.',
781
800
  target ? `Resolved E10 baseUrl: ${target}.` : 'No E10 baseUrl could be resolved.',
782
- 'This web/Linux host cannot complete a localhost browser OAuth callback.',
801
+ 'The auth xiaoe command never falls back to browser OAuth.',
783
802
  'Refresh the XiaoE web session or restore host credential injection before retrying.',
784
803
  ].join(' ');
785
804
  return Object.assign(new CliError(message, ExitCode.AuthError), {
786
805
  type: 'authentication',
787
- subtype: 'xiaoe_web_credential_unavailable',
806
+ subtype: 'xiaoe_credential_unavailable',
788
807
  retryable: false,
789
808
  stage: 'xiaoe_unified_login',
790
809
  });
@@ -865,7 +884,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
865
884
  profileBaseUrl: options.profileBaseUrl,
866
885
  env,
867
886
  });
868
- const mayUseTokenScript = options.allowTokenScript || hasXiaoEEnv(env);
887
+ const mayUseTokenScript = options.allowTokenScript === true;
869
888
  if (!baseUrl && !mayUseTokenScript)
870
889
  return null;
871
890
  let credential = null;
@@ -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
+ }