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.
- package/README.md +35 -30
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +1 -1
- package/dist/core/runtime-agent.js +19 -0
- package/dist/internal/e10/auth/commands.js +48 -33
- package/dist/internal/e10/auth/messages.js +3 -0
- package/dist/internal/e10/auth/session.js +8 -0
- package/dist/internal/e10/auth/xiaoe.js +62 -43
- package/dist/internal/e10/auth-recovery.js +148 -0
- package/dist/internal/e10/client.js +47 -15
- package/dist/internal/e10/context.js +9 -31
- package/dist/internal/e10/index.js +1 -0
- package/dist/internal/e10/request-runtime.js +51 -9
- package/dist/shortcuts/archive/host.js +3 -2
- package/dist/shortcuts/archive/manifest.js +1 -1
- package/dist/shortcuts/archive-oa/constants.js +66 -0
- package/dist/shortcuts/archive-oa/continuation.js +70 -0
- package/dist/shortcuts/archive-oa/errors.js +53 -0
- package/dist/shortcuts/archive-oa/host.js +158 -0
- package/dist/shortcuts/archive-oa/index.js +225 -0
- package/dist/shortcuts/archive-oa/manifest.js +62 -0
- package/dist/shortcuts/archive-oa/operations/borrow-car.js +159 -0
- package/dist/shortcuts/archive-oa/operations/borrow-url.js +108 -0
- package/dist/shortcuts/archive-oa/operations/confirm.js +48 -0
- package/dist/shortcuts/archive-oa/operations/contract.js +120 -0
- package/dist/shortcuts/archive-oa/operations/locate.js +50 -0
- package/dist/shortcuts/archive-oa/operations/registry.js +40 -0
- package/dist/shortcuts/archive-oa/operations/shared.js +181 -0
- package/dist/shortcuts/archive-oa/operations/types.js +45 -0
- package/dist/shortcuts/archive-oa/operations.js +13 -0
- package/dist/shortcuts/asset/host.js +7 -6
- package/dist/shortcuts/blog/host.js +4 -3
- package/dist/shortcuts/caizhilian/host.js +3 -2
- package/dist/shortcuts/caizhilian/manifest.js +1 -1
- package/dist/shortcuts/calendar/host.js +2 -1
- package/dist/shortcuts/contract/host.js +34 -59
- package/dist/shortcuts/ehr/host.js +2 -1
- package/dist/shortcuts/esb/host.js +2 -1
- package/dist/shortcuts/fna/host.js +6 -3
- package/dist/shortcuts/fna/manifest.js +10 -5
- package/dist/shortcuts/fna/operations/expense-chart.js +162 -0
- package/dist/shortcuts/fna/operations/registry.js +2 -0
- package/dist/shortcuts/hrm/host.js +2 -1
- package/dist/shortcuts/im/host.js +3 -2
- package/dist/shortcuts/index.js +2 -0
- package/dist/shortcuts/invoice/host.js +9 -7
- package/dist/shortcuts/invoice/manifest.js +38 -47
- package/dist/shortcuts/invoice/operations/issuing-qys.js +328 -0
- package/dist/shortcuts/invoice/operations/registry.js +2 -3
- package/dist/shortcuts/jiuchuanhui/host.js +2 -1
- package/dist/shortcuts/mail/host.js +2 -1
- package/dist/shortcuts/meeting/host.js +2 -1
- package/dist/shortcuts/meeting/operations/eb/conflict.js +78 -12
- package/dist/shortcuts/okr/host.js +2 -1
- package/dist/shortcuts/plan/host.js +2 -1
- package/dist/shortcuts/project/host.js +2 -1
- package/dist/shortcuts/workflow/endpoints.js +11 -0
- package/dist/shortcuts/workflow/host.js +5 -7
- package/dist/shortcuts/workflow/index.js +6 -36
- package/dist/shortcuts/workflow/operations/chatscene.js +252 -0
- package/dist/shortcuts/workflow/operations/form.js +1047 -191
- package/dist/shortcuts/workflow/operations/read.js +137 -8
- package/dist/shortcuts/workflow/operations/sign-input.js +106 -0
- package/dist/shortcuts/workflow/operations/write.js +9 -1
- package/dist/shortcuts/workflow/stdout-compact.js +16 -1
- package/docs/SKILL.md +1 -1
- package/docs/_catalog.md +1 -1
- package/docs/agent-invoice.md +1 -1
- package/docs/archive-oa.md +79 -0
- package/docs/caizhilian.md +1 -1
- package/docs/e10-auth.md +7 -6
- package/docs/fna.md +7 -3
- package/docs/invoice.md +4 -6
- package/package.json +1 -1
- package/skill-template/business-info.json +20 -1
- package/skill-template/domains/archive-oa.md +19 -0
- package/skill-template/domains/caizhilian.md +2 -1
- package/skill-template/domains/qiyecheng.md +1 -0
- package/skill-template/domains/yepiaotong.md +8 -4
- package/skill-template/skill-template.md +1 -0
- package/skills/weaver-e10-archive-oa/SKILL.md +159 -0
- package/skills/weaver-e10-archive-oa/references/archive-locate.md +49 -0
- package/skills/weaver-e10-archive-oa/references/borrow-car.md +76 -0
- package/skills/weaver-e10-archive-oa/references/borrow-flow.md +53 -0
- package/skills/weaver-e10-archive-oa/references/contract-search.md +63 -0
- package/skills/weaver-e10-archive-oa/references/disabled-capabilities.md +29 -0
- package/skills/weaver-e10-archive-oa/references/qys-contract-search.md +47 -0
- package/skills/weaver-e10-archive-oa/references/source-manifest.json +400 -0
- package/skills/weaver-e10-blog/SKILL.md +6 -1
- package/skills/weaver-e10-blog/references/blog-query.md +3 -1
- package/skills/weaver-e10-builder/SKILL.md +22 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +47 -16
- package/skills/weaver-e10-caizhilian/references/action-flow.md +70 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +12 -4
- package/skills/weaver-e10-caizhilian/references/auth-and-install.md +207 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +5 -3
- package/skills/weaver-e10-caizhilian/references/document.md +33 -11
- package/skills/weaver-e10-caizhilian/references/file-collection.md +57 -54
- package/skills/weaver-e10-caizhilian/references/file-upload.md +5 -6
- package/skills/weaver-e10-caizhilian/references/files.md +9 -3
- package/skills/weaver-e10-caizhilian/references/folder.md +48 -16
- package/skills/weaver-e10-caizhilian/references/host-executor-prompt.md +32 -0
- package/skills/weaver-e10-caizhilian/references/host-platform-integration.md +46 -0
- package/skills/weaver-e10-caizhilian/references/rule-index.md +56 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +17 -54
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +16 -63
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +24 -64
- package/skills/weaver-e10-caizhilian/references/setting-storage-docs-summary.md +126 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage-field-map.md +57 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +143 -548
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +7 -7
- package/skills/weaver-e10-caizhilian/references/task-workflow.md +87 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +21 -45
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +7 -0
- package/skills/weaver-e10-calendar/SKILL.md +6 -1
- package/skills/weaver-e10-esb/SKILL.md +8 -0
- package/skills/weaver-e10-esb/references/input-format.md +3 -1
- package/skills/weaver-e10-esb/references/source-manifest.json +5 -5
- package/skills/weaver-e10-hrm/SKILL.md +6 -1
- package/skills/weaver-e10-hrm/references/employee.md +1 -1
- package/skills/weaver-e10-hrm/references/org.md +1 -1
- package/skills/weaver-e10-hrm/references/position.md +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +1 -1
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +6 -1
- package/skills/weaver-e10-jucailin/SKILL.md +6 -1
- package/skills/weaver-e10-jucailin/references/hr-contact.md +1 -1
- package/skills/weaver-e10-jucailin/references/salary.md +1 -1
- package/skills/weaver-e10-mail/SKILL.md +6 -1
- package/skills/weaver-e10-meeting/SKILL.md +11 -6
- package/skills/weaver-e10-meeting/references/meeting-eb.md +7 -3
- package/skills/weaver-e10-meeting/references/meeting-queries.md +3 -1
- package/skills/weaver-e10-meeting/references/meeting-verification.md +4 -2
- package/skills/weaver-e10-okr/SKILL.md +6 -1
- package/skills/weaver-e10-okr/references/source-manifest.json +5 -5
- package/skills/weaver-e10-plan/SKILL.md +6 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +3 -1
- package/skills/weaver-e10-plan/references/plan-report-query.md +3 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +7 -7
- package/skills/weaver-e10-qiyecheng/SKILL.md +16 -6
- package/skills/weaver-e10-qiyecheng/references/catalog.md +6 -3
- package/skills/weaver-e10-qiyecheng/references/expense-center.md +2 -0
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +78 -0
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +87 -33
- package/skills/weaver-e10-shared/SKILL.md +53 -28
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +22 -20
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +13 -13
- package/skills/weaver-e10-shared/references/weaver-e10-installation.md +3 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +6 -1
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
- package/skills/weaver-e10-workflow/references/source-manifest.json +33 -24
- package/skills/weaver-e10-workflow/references/workflow-create.md +41 -26
- package/skills/weaver-e10-yepiaotong/SKILL.md +23 -18
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +87 -45
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +152 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +210 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +59 -36
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +63 -512
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +205 -346
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -5
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +231 -344
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +5 -5
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +201 -162
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +121 -145
- package/skills/weaver-e10-yimiaoban/SKILL.md +9 -2
- package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +9 -9
- package/skills/weaver-e10-ziguanjia/SKILL.md +6 -1
- package/dist/shortcuts/invoice/operations/issuing.js +0 -198
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-field-rules.md +0 -161
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +0 -107
- 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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
248
|
-
|
|
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');
|
package/dist/cmd/setup/index.js
CHANGED
|
@@ -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('
|
|
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,
|
|
9
|
-
export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential,
|
|
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,
|
|
478
|
+
function xiaoEUnifiedLoginUnavailableMessage(baseUrl, attempted) {
|
|
484
479
|
return [
|
|
485
|
-
|
|
486
|
-
? '
|
|
487
|
-
: '
|
|
480
|
+
attempted
|
|
481
|
+
? 'XiaoE 统一登录已尝试,但未获取到可用会话。'
|
|
482
|
+
: '未检测到可用的 XiaoE 统一登录凭据源,本次未尝试统一登录。',
|
|
483
|
+
'`auth xiaoe` 不会降级到浏览器 OAuth。',
|
|
488
484
|
baseUrl ? `当前 E10 baseUrl: ${baseUrl}。` : '未从 XiaoE 凭证中解析到 E10 baseUrl。',
|
|
489
|
-
|
|
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
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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 =
|
|
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
|
-
|
|
533
|
+
'authMode=xiaoe',
|
|
534
|
+
`credentialSourceDetected=${String(xiaoESources.any)}`,
|
|
520
535
|
`attempted=${String(useXiaoELogin)}`,
|
|
521
|
-
`
|
|
522
|
-
`
|
|
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 =
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
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', '
|
|
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: '
|
|
830
|
+
browserFallback: 'never',
|
|
816
831
|
});
|
|
817
832
|
auth.command('logout')
|
|
818
833
|
.alias('clear')
|
|
@@ -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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
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
|
-
//
|
|
97
|
-
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
'
|
|
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: '
|
|
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
|
|
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
|
+
}
|